HowToCode - Adapting a systems approach to development for React and TypeScript

Probably every programmer sooner or later begins to think about the quality of his code. And, most likely, I will not be mistaken if I say that a good half of the developers are always unhappy with them. I rarely liked my code either: the functions, it seemed, could be made shorter, it would be nice to remove unnecessary nesting too. It would be great to write tests and documentation, but almost never had time for them.





Naturally, I spent a lot of time reading books and all kinds of articles, trying to figure out how to make my code better. But the picture didn't add up. Either the recommendations in books or articles were too general and sometimes contradictory, or it was in me, but, despite the efforts, there was little result.





The situation changed radically after I took the HowToCode course [link removed by the moderator, since breaks the rules] . The course describes a systematic and, like everything brilliant, a simple and beautiful approach to development, which brings together analysis, design, documentation, testing and code development. The entire course is built on the use of the functional paradigm and the Scheme language (a Lisp dialect), however, the recommendations are quite applicable for other languages, and for JavaScript and TypeScript, to which I tried to adapt them, they are generally fine.





I really liked the results:





  • First, finally my code became readable, clear documentation and tests appeared.





  • Secondly, the TDD approach worked, to which I took several approaches, but could not start to follow it in any way





  • Thirdly, I practically got rid of debugging: the very process when the code has already been written, but does not work yet or does not work as it should, which is wildly annoying





  • Well, and one more very pleasant observation: I'm not afraid to forget or miss something, because at every moment of time I am working on one specific small task, and all the other tasks are written down.





  • Yes, and, anticipating the questions, I will say - it feels like the development time, if it slowed down, then not much, even taking into account the writing of tests and documentation. And the savings in maintaining code are enormous.





But let's get to the point and see how this approach works.





General idea

, , - .





, , .





3 :

















, , , , .





. : , Jira . .





, 2 : . , - . , - , , .





, , . .





:





1.





2. , .





1.

, :









  • : , , - -









, . - , , , , - , .





, :  " web- , , . ."





, , . , , , ( ).









, , - . :









, , , . :





, , , . , - :





, , - . 





2. ,

: 3 , : , .









- , .  , , :





  • : , , , , ..





  • , , ,





  • : , ..









, . :





  • ,





  • ,









, , :









- . , - , . :













  • , ..









: ? , , , , , - .





:





  • :





  • , , , , ,





  • , , ,





  • , , .





, :









, - , . , , .





:





  • : , , , ..





  • :









  • ..





, , :













, .





:





  1. -,





  2. -, ,





  3. -, , , . , , , .





, . , , , .





- .





, , , . 





?





  1. -, . , , . , , .





  2. -, TypeScript, .





  3. -, , , unit-.





:













  1. - , .





  2. - .





. TypeScript JSDoc, - . - React JS, , (props) (state), React. , , , .





: HowToCode , - , . , , .





, .





  , , Redux, .





. . , AppState - :





export interface AppState {}
      
      



. , , , . :

































title













+













backendAddress













+













isLoading













+





true -





false -









group









Group





-













loadData













+









:





export interface AppState {    
    title: string;    
    backendAddress: string;
    isLoading: boolean;
    group?: Group;
    loadData: Function;
}
      
      







TypeScript , , , . ,  , .  JavaScript - JSDoc.





/**
 *   
 * @prop title -  
 * @prop backendAddress -  
 * @prop isLoading -    (true - , false - )
 * @prop group -    .     group  
 * @method loadData -   
 */
export interface AppState {
    title: string;
    backendAddress: string;
    isLoading: boolean;
    group?: Group;
    loadData: Function;
}
      
      



AppState Group. , AppState , - , , . . , . .





, , TODO - IDE TODO





/**
 *   
 * @prop title -  
 * @prop backendAddress -  
 * @prop isLoading -    (true - , false - )
 * @prop group -    .     group  
 * @method loadData -   
 */
export interface AppState {
    title: string;
    backendAddress: string;
    isLoading: boolean;
    group?: Group;
    loadData: Function;
}


/**
 *    
*/
//TODO
export interface Group {

}
      
      



, .





, , - . , unit-, , - . , group - , . - :





/**
 *   
 * @prop title -  
 * @prop backendAddress -  
 * @prop isLoading -    (true - , false - )
 * @prop group -    .     group  
 * @method loadData -   
 */
export interface AppState {
    title: string;
    backendAddress: string;
    isLoading: boolean;
    group?: Group;
    loadData: Function;
}

// 1
const appState1: AppState = {
    title: " 1",
    backendAddress: "/view_doc.html",
    isLoading: true,
    group: undefined,
    loadData: () => {}
}

// 2
const appState2: AppState = {
    title: " 2",
    backendAddress: "/view_doc_2.html",
    isLoading: false,
    group: group1, //   
    loadData: () => {}
}


/**
 *    
*/
//TODO
export interface Group {
}

//TODO
const group1 = {}
      
      



, , , - . , , , - , "".





TODO , , , , .





. , , , , - :





export default abstract class AbstractService {
       
    /**
     *       
     * @fires get_group_data -  ,   
     * @returns   
     */
    abstract getGroupData(): Promise<Group>;

}
      
      



, - , , .





, .





, , . 





, - :





  1. . - , :













    • ,





  2. ,





  3. . .





  4. .





  5. , :









    1. (TODO),





,





React - , .





, , - , .





1 -

- , :





  • ,









  • , , .





TypeScript - :





export const getWorkDuration = (worktimeFrom: string, worktimeTo: string): string => {
    return "6 18";
}
      
      



:





  • getWorkDuration - , ,





  • worktimeFrom: string, worktimeTo: string - .





  • : string -





  • return "6 18" - ,





, , - unit - , .





2 : - . :





:





const componentName = (props: PropsType) => { return <h1>componentName</h1> }
      
      



:





class componentName extends React.Component<PropsType, StateType>{

    state = {
        //      
    }

    render() {        
        return <h1>componentName</h1> 
    }

}
      
      



:





  • PropsType -





  • StateType -





- , , .





, App. , , , -. :





interface AppProps {}

export default class App extends Component<AppProps, AppState> {
    state = {
        title: "  ",
        backendAddress: "",
        isLoading: true,
        loadData: this.loadData.bind(this)
    }

    /**
     *     
     */
    //TODO
    loadData() {

    }

    render() {
        return <h1>App</h1>
    }
}
      
      



:





  1. App "", AppProps





  2. AppState , ,





  3. loadData, , TODO,





2 -

, , , . JSDoc, , , .





, , , , , , .  , :





/**
 *           
 *       ,  ,    -    
 * @param worktimeFrom -      : ( 00:00  23:59)
 * @param worktimeTo -      : ( 00:00  23:59)
 * @return           Y?,  6 18  5,    
 */
//TODO
export const getWorkDuration = (worktimeFrom: string, worktimeTo: string): string => {
    return "6 18";
}
      
      



TODO,   , .





- , , , , . , , , , - , - .





3.

, , . - unit-. , :





  • unit- , , - ,





  • unit- , . ,





  • , .





.  , .









" " - , .





, , . "" enum. :





type TrafficLights = "" | "" | "";
      
      



, , TrafficLights :  , , - :





function trafficLightsFunction (trafficLights: TrafficLights) {
    switch (trafficLights) {
        case "":
            ...
        case "":
            ...
        case "":
            ...
    }
}
      
      



. , TrafficLights - , , "..." , . 





, . , , , , 3 . .





, - , .





, - . , - , - .   " " . , , , , . .





:





β„–





















1









, .

















1





- 2 (true / false)





- 1 , 0





2









, .

















..





, , , .





, 100- , , 4 :





1 - 25,





26 - 50,





51 - 75,





76 - 100





4 .





3













(0 - 300]









..





:













4









, ,  





? ,





- , , . ,





. . " " - . - , , .





, , -, .





2 :













- , .





5





()





,





"" :





id

















..





2 .  





6









,









,





, :





,





,





, , 2





, .





/**
 *           
 *       ,  ,    -    
 * @param worktimeFrom -      : ( 00:00  23:59)
 * @param worktimeTo -      : ( 00:00  23:59)
 * @return           Y?,  6 18  5,    
 */
//TODO
export const getWorkDuration = (worktimeFrom: string, worktimeTo: string): string => {
    return "6 18";
}
      
      



, . string , - , :, , 00:00 23:59. , . , . 3 -:





  1. ,





  2. , -





  3. -





, . -, , 3 , , , 5





β„–









worktimeFrom





worktimeTo









1





worktimeFrom





, ,





"24:00"





,





"18:00"









2





worktimeFrom





,





"18:00"





, ,





"24:00"









3





,





worktimeFrom < worktimeTo





, worktimeTo,





"00:00"





, worktimeFrom, ,





"23:59"





23 59





4





,





worktimeFrom > worktimeTo





, worktimeTo,





"18:49"





, worktimeFrom, ,





"10:49"





16





5





worktimeFrom = worktimeTo





,  ,





"01:32"





,  ,





"01:32"





0





- , : .  . Jest Enzyme - React JS. , :





describe('       ', () => {
    
    it('     ,   0', ()=>{
        const result = getWorkDuration("01:32", "01:32");
        expect(result).toBe("0");
    });
    
    //    
    ...

});
      
      



- : , , . -, , , - enzyme. 





App. :





interface AppProps {}

export default class App extends Component<AppProps, AppState> {
    state = {
        title: "  ",
        backendAddress: "",
        isLoading: true,
        loadData: this.loadData.bind(this)
    }

    /**
     *     
     */
    //TODO
    loadData() {

    }

    render() {
        return <h1>App</h1>
    }
}
      
      



, , :





/**
 *   
 * @prop title -  
 * @prop backendAddress -  
 * @prop isLoading -    (true - , false - )
 * @prop group -    .     group  
 * @method loadData -   
 */
export interface AppState {
    title: string;
    backendAddress: string;
    isLoading: boolean;
    group?: Group;
    loadData: Function;
}
      
      



, :





  • -, , .





  • -, . , group , , - .  , "". 





, , :





  • () 2 ,





  • , .  , : .





, 4 :





  • 2 - , , "" , ,





  • 2 - , ,





, , 2 .





loadData - , , - , - . , , , , , .





- loadData :





import React from 'react';
import Enzyme, { mount, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

import App from './App';

describe('App', () => {
    test('  App ,   loadData', () => {

        //    loadData
        const loadData = jest.spyOn(App.prototype, 'loadData');
        
        // 
        const wrapper = mount(<App></App>);
        
        //    loadData
        expect(loadData.mock.calls.length).toBe(1);
    });
}
      
      



enzyme , .  :





  1. loadData ,





  2. ( )





  3. ,





- .





test('  ,     ', () => {
        // 
        const wrapper = mount(<App></App>);

        //      
        wrapper.setState({
            title: " 1",
            backendAddress: "/view_doc.html",
            isLoading: true,
            group: undefined,
            loadData: () => {}
        })

        //   
        //    
        expect(wrapper.find('h1').length).toBe(1);
        expect(wrapper.find('h1').text()).toBe(" 1");

        //,    
        expect(wrapper.find(Spinner).length).toBe(1);

        //,     
        expect(wrapper.find(Group).length).toBe(0);
    });
      
      



:









  1. . , . ,









, , 2 , :





  • -





  • . , , .





, , .





:





test('  ,    .   ', () => {
        const wrapper = mount(<App></App>);

        wrapper.setState({
            title: " 2",
            backendAddress: "/view_doc_2.html",
            isLoading: false,
            group: {
                id: "1",
                name: " 1",
                listOfCollaborators: []
            },
            loadData: () => {}
        })

        expect(wrapper.find('h1').length).toBe(1);
        expect(wrapper.find('h1').text()).toBe(" 2");
        expect(wrapper.find(Spinner).length).toBe(0);
        expect(wrapper.find(Group).length).toBe(1);
    });
      
      



, , , , .  , , , .





, , . , .





4 5.

, . , , , . , :





  • -, ,





  • -, , Knowledge Shift ( ). 









. , , , . , , - .





, . :





/**
 *           
 *       ,  ,    -    
 * @param worktimeFrom -      : ( 00:00  23:59)
 * @param worktimeTo -      : ( 00:00  23:59)
 * @return           Y?,  6 18  5,    
 */
//TODO
export const getWorkDuration = (worktimeFrom: string, worktimeTo: string): string => {
    return "6 18";
}
      
      



, , , ":", , :





  1. , , , 00:00





  2. , ,





  3. X Y?,





:





  • ":", ..





  • .





:





/**
 *           
 *       ,  ,    -    
 * @param worktimeFrom -      : ( 00:00  23:59)
 * @param worktimeTo -      : ( 00:00  23:59)
 * @return           Y?,  6 18  5,    
 */
export const getWorkDuration = (worktimeFrom: string, worktimeTo: string): string => {
    const worktimeFromInMinutes = getWorktimeToMinutes(worktimeFrom);
    const worktimeToInMinutes = getWorktimeToMinutes(worktimeTo);
    const minutesDiff = calcDiffBetweenWorktime(worktimeFromInMinutes, worktimeToInMinutes);
    return convertDiffToString(minutesDiff);
}


/**
 * c  ,    
 * @param worktimeFrom -    : ( 00:00  23:59)
 * @returns  ,   00 00
 */
//TODO
export const getWorktimeToMinutes = (worktime: string): number => {
    return 0;
}


/**
 *            
 * @param worktimeFrom -        ,    
 * @param worktimeTo -        ,    
 * @returns           
 */
//TODO
export const calcDiffBetweenWorktime = (worktimeFrom: number, worktimeTo: number): number => {
    return 0;
}


/**
 *         Y?
 * @param minutes -  
 * @returns     Y?,  6 18  5
 */
//TODO
export const convertDiffToString = (minutes: number): string => {
    return "6 18";
}
      
      



, , , . . , TODO, . . , , .





, . , , , , - , .





Knowledge Shift





, , Knowledge Shift.





, , , , , , - () . , , , .





, , - , , . Knowledge Domain Shift Knowledge Shift.





, , , , .





, ,   - App:





export default class App extends Component<AppProps, AppState> {
    state = {
        title: "  ",
        backendAddress: "",
        isLoading: true,
        group: undefined,
        loadData: this.loadData.bind(this)
    }

    /**
     *     
     */
    //TODO
    loadData() {}


    componentDidMount() {
        //  loadData   
        this.loadData();
    }


    render() {
        const {isLoading, group, title} = this.state;
        return (
            <div className="container">
                <h1>{title}</h1>
                {
                    isLoading ?
                    <Spinner/>
                    //  Group     
                    : <Group group={group}></Group>
                }
            </div>
        );
    }
}
      
      



componentDidMount, . render. , , - , Group.





, , c . group , , App - , .  , , , , , , .





, , . , , TODO : + . , TODO .





When that wonderful moment arrives, you simply launch the application and enjoy how it simply works. It doesn't crash because of missed errors or forgotten and unrealized scenario, but just works.





This is, in general, the whole approach. It is not difficult, but it takes habit and discipline. As with any tricky business, the biggest challenge is getting started and resisting the urge to quit in the first couples. If this succeeds, then after a while you will not even want to think about how to write code the old fashioned way: without tests, documentation and with long incomprehensible functions. Good luck!








All Articles