HowToCode: adaptación de un enfoque de sistemas al desarrollo para React y TypeScript

Probablemente todos los programadores, tarde o temprano, empiecen a pensar en la calidad de su código. Y lo más probable es que no me equivoque si digo que una buena mitad de los desarrolladores siempre están descontentos con ellos. Rara vez me gustó mi código tampoco: las funciones, al parecer, podrían hacerse más cortas, sería bueno eliminar también el anidamiento innecesario. Sería genial escribir pruebas y documentación, pero casi nunca tuve tiempo para ello.





Naturalmente, pasé mucho tiempo leyendo libros y todo tipo de artículos, tratando de descubrir cómo mejorar mi código. Pero la imagen no cuadraba. O las recomendaciones en libros o artículos eran demasiado generales y, a veces, contradictorias, o estaba en mí, pero, a pesar de los esfuerzos, hubo pocos resultados.





La situación cambió radicalmente después de que hice el curso HowToCode [enlace eliminado por el moderador, ya que rompe las reglas] . El curso describe un enfoque sistemático y, como todo brillante, simple y hermoso para el desarrollo, que combina análisis, diseño, documentación, pruebas y desarrollo de código. Todo el curso se basa en el uso del paradigma funcional y el lenguaje Scheme (un dialecto Lisp), sin embargo, las recomendaciones son bastante aplicables para otros lenguajes, y para JavaScript y TypeScript, a los que intenté adaptarlos, generalmente son multa.





Realmente me gustaron los resultados:





  • Primero, finalmente mi código se volvió legible, aparecieron pruebas y documentación clara.





  • En segundo lugar, el enfoque TDD funcionó, al que tomé varios enfoques, pero no pude comenzar a seguirlo de ninguna manera.





  • -, : , , ,





  • : - , , -





  • , , , - , , . - .





, .





, , - .





, , .





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 .





Cuando llegue ese maravilloso momento, simplemente inicie la aplicación y disfrute de cómo funciona. No se bloquea debido a errores perdidos o escenarios olvidados y no realizados, sino que simplemente funciona.





Este es, en general, el enfoque completo. No es difícil, pero requiere hábito y disciplina. Al igual que con cualquier negocio complicado, el mayor desafío es comenzar y resistir la tentación de dejar de fumar en las primeras parejas. Si esto tiene éxito, después de un tiempo ni siquiera querrá pensar en cómo escribir código a la antigua usanza: sin pruebas, documentación y con funciones largas e incomprensibles. ¡Buena suerte!








All Articles