Clean Code

정리하자 정리! 코드 정리!

https://github.com/ryanmcdermott/clean-code-javascript

위 링크에 있는 깃헙 ryanmcdermott 의 clean-code-javascript 에서 내용을 참고하여 만든 클린 코드 가이드를 TypeScript 에서 사용할 수 있도록 정리한 글입니다.

영어로 된 가이드 글을 번역하고 (고마워요 파파고) 제 생각에 중요하다 느낀 것들만 추려 모아 봤습니다.

이 곳에 적힌 모든 가이드를 따르긴 힘들겠지만, 이런게 있다라고 읽어보고 코드에 적용해보려는 노력을 하는게 클린 코드로 나아가는 한 걸음이라고 생각합니다!

이번 편은 변수와 함수에 대해 포스팅하겠습니다.


장문입니다..읽기 전 숨 한 번 쉬고 갈까요?


변수

의미 있고 발음하기 쉬운 변수 이름


안좋은 예:

function between<T>(a1: T, a2: T, a3: T): boolean {
    return a2 <= a1 && a1 <= a3;
}

좋은 예:

function between<T>(value: T, left: T, right: T): boolean {
    return left <= value && value <= right;
}

👆맨 위로


동일한 유형의 변수에 동일한 어휘를 사용하세요.


안좋은 예:

function getUserInfo(): User;
function getUserDetails(): User;
function getUserData(): User;

좋은 예:

function getUser(): User;

👆맨 위로


검색 가능한 이름을 사용하세요.


안좋은 예:

// 86400000 의 의미는 뭘까요?
setTimeout(blastOff, 86400000);

좋은 예:

const MILLISECONDS_IN_A_DAY = 8640000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

👆맨 위로


자신만 알아볼 수 있는 작명을 피하세요.


안좋은 예:

declare const users: Map<string, User>;

for (const keyValue of users) {
    // iterate through users map
    // keyValue 는 뭘 의미하나요?
}

좋은 예:

declare const users: Map<string, User>;

for (const [id, user] of users) {
    // iterate through users map
}

👆맨 위로


암시적으로 이름을 짓지 마세요.


안좋은 예:

const u = getUser();
const s = getSubscription();
const t = charge(u, s);

좋은 예:

const user = getUser();
const subscription = getSubscription();
const transaction = charge(user, subscription);

👆맨 위로


문맥상 필요없는 것들을 쓰지 마세요.


안좋은 예:

type Car = {
    carMake: string;
    carModel: string;
    carColor: string;
}

function print(car: Car): void {
    console.log(`${car.carMake} ${car.carModel} (${car.carColor})`);
}

좋은 예:

type Car = {
    make: string;
    model: string;
    color: string;
}

function print(car: Car): void {
    console.log(`${car.make} ${car.model} (${car.color})`);
}

👆맨 위로


기본 매개변수가 short circuiting 트릭이나 조건문 보다 깔끔합니다


안좋은 예:

function loadPages(count?: number) {
    const loadCount = count !== undefined ? count : 10;
    // ...
}

좋은 예:

function loadPages(count: number = 10) {
    // ...
}

👆맨 위로


enum 으로 의도를 코드에 표현해 주세요.


안좋은 예:

const GENRE = {
    ROMANTIC: 'romantic',
    DRAMA: 'drama',
    COMEDY: 'comedy',
    DOCUMENTARY: 'documentary',
}

projector.configureFilm(GENRE.COMEDY);

class Projector {
    // delactation of Projector
    configureFilm(genre) {
    switch (genre) {
        case GENRE.ROMANTIC:
        // some logic to be executed 
    }
    }
}

좋은 예:

enum GENRE {
    ROMANTIC,
    DRAMA,
    COMEDY,
    DOCUMENTARY,
}

projector.configureFilm(GENRE.COMEDY);

class Projector {
    // delactation of Projector
    configureFilm(genre) {
    switch (genre) {
        case GENRE.ROMANTIC:
        // some logic to be executed 
    }
    }
}

👆맨 위로



함수



함수 인수를 2개 이하로 줄이세요.


  • 매개 변수가 3개 이상이 되면, 테스트 해야하는 경우의 수가 많아집니다.
  • 1개나 2개의 인자를 가지고 있는 것이 가장 이상적인 케이스입니다.
  • 2개 이상의 인자를 가진 함수라면 너무 많은 일을 하는 함수일 가능성이 있습니다.

안좋은 예:

function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) {
    // ...
}

createMenu('Foo', 'Bar', 'Baz', true);

좋은 예:

function createMenu(options: { title: string, body: string, buttonText: string, cancellable: boolean }) {
    // ...
}

createMenu({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
});

Type aliases 를 사용하면 가독성을 향상 시킬 수 있습니다.

type MenuOptions = { title: string, body: string, buttonText: string, cancellable: boolean };

function createMenu(options: MenuOptions) {
    // ...
}

createMenu({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
});

👆맨 위로


함수는 한 가지 일을 해야합니다.


  • 소프트웨어 엔지니어링에서 가장 중요한 규칙입니다.
  • 함수가 둘 이상의 작업을 수행하면 작성, 테스트 및 추론하기가 더 어렵습니다.

    안좋은 예:
    
    function emailClients(clients: Client[]) {
    clients.forEach((client) => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
        email(client);
    }
    });
    }
    

좋은 예:

function emailClients(clients: Client[]) {
    clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client: Client) {
    const clientRecord = database.lookup(client);
    return clientRecord.isActive();
}

👆맨 위로


함수명은 함수가 무엇을 하는지 알 수 있어야 합니다


안좋은 예:

function addToDate(date: Date, month: number): Date {
    // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
addToDate(date, 1);

좋은 예:

function addMonthToDate(date: Date, month: number): Date {
    // ...
}

const date = new Date();
addMonthToDate(date, 1);

👆맨 위로


함수는 단일 행동을 추상화 해야합니다.


안좋은 예:

function parseCode(code: string) {
    const REGEXES = [ /* ... */ ];
    const statements = code.split(' ');
    const tokens = [];

    REGEXES.forEach((regex) => {
    statements.forEach((statement) => {
        // ...
    });
    });

    const ast = [];
    tokens.forEach((token) => {
    // lex...
    });

    ast.forEach((node) => {
    // parse...
    });
}

좋은 예:

const REGEXES = [ /* ... */ ];

function parseCode(code: string) {
    const tokens = tokenize(code);
    const syntaxTree = parse(tokens);

    syntaxTree.forEach((node) => {
    // parse...
    });
}

function tokenize(code: string): Token[] {
    const statements = code.split(' ');
    const tokens: Token[] = [];

    REGEXES.forEach((regex) => {
    statements.forEach((statement) => {
        tokens.push( /* ... */ );
    });
    });

    return tokens;
}

function parse(tokens: Token[]): SyntaxTree {
    const syntaxTree: SyntaxTree[] = [];
    tokens.forEach((token) => {
    syntaxTree.push( /* ... */ );
    });

    return syntaxTree;
}

👆맨 위로


중복된 코드를 작성하지 마세요.


안좋은 예:

function showDeveloperList(developers: Developer[]) {
    developers.forEach((developer) => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();

    const data = {
        expectedSalary,
        experience,
        githubLink
    };

    render(data);
    });
}

function showManagerList(managers: Manager[]) {
    managers.forEach((manager) => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();

    const data = {
        expectedSalary,
        experience,
        portfolio
    };

    render(data);
    });
}

좋은 예:

class Developer {
    // ...
    getExtraDetails() {
    return {
        githubLink: this.githubLink,
    }
    }
}

class Manager {
    // ...
    getExtraDetails() {
    return {
        portfolio: this.portfolio,
    }
    }
}

function showEmployeeList(employee: Developer | Manager) {
    employee.forEach((employee) => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();
    const extra = employee.getExtraDetails();

    const data = {
        expectedSalary,
        experience,
        extra,
    };

    render(data);
    });
}

👆맨 위로


Object.assign을 사용해 기본 객체를 만드세요


안좋은 예:

type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu(config: MenuConfig) {
    config.title = config.title || 'Foo';
    config.body = config.body || 'Bar';
    config.buttonText = config.buttonText || 'Baz';
    config.cancellable = config.cancellable !== undefined ? config.cancellable : true;

    // ...
}

createMenu({ body: 'Bar' });

좋은 예:

type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu(config: MenuConfig) {
    const menuConfig = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
    }, config);

    // ...
}

createMenu({ body: 'Bar' });
  • 기본 값을 설정할 수도 있습니다.

    type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };
    
    function createMenu({ title = 'Foo', body = 'Bar', buttonText = 'Baz', cancellable = true }: MenuConfig) {
    // ...
    }
    
    createMenu({ body: 'Bar' });
    

👆맨 위로


매개 변수로 플래그를 사용하지 마세요.


안좋은 예:

function createFile(name: string, temp: boolean) {
    if (temp) {
    fs.create(`./temp/${name}`);
    } else {
    fs.create(name);
    }
}

좋은 예:

function createTempFile(name: string) {
    createFile(`./temp/${name}`);
}

function createFile(name: string) {
    fs.create(name);
}

👆맨 위로


전역 함수를 사용하지 마세요.


안좋은 예:

declare global {
    interface Array<T> {
    diff(other: T[]): Array<T>;
    }
}

if (!Array.prototype.diff) {
    Array.prototype.diff = function <T>(other: T[]): T[] {
    const hash = new Set(other);
    return this.filter(elem => !hash.has(elem));
    };
}

좋은 예:

class MyArray<T> extends Array<T> {
    diff(other: T[]): T[] {
    const hash = new Set(other);
    return this.filter(elem => !hash.has(elem));
    };
}

👆맨 위로


조건문을 캡슐화하세요.


안좋은 예:

if (subscription.isTrial || account.balance > 0) {
    // ...
}

좋은 예:

function canActivateService(subscription: Subscription, account: Account) {
    return subscription.isTrial || account.balance > 0
}

if (canActivateService(subscription, account)) {
    // ...
}

👆맨 위로


부정적인 조건문을 쓰지 마세요.


안좋은 예:

function isEmailNotUsed(email: string): boolean {
    // ...
}

if (isEmailNotUsed(email)) {
    // ...
}

좋은 예:

function isEmailUsed(email): boolean {
    // ...
}

if (!isEmailUsed(node)) {
    // ...
}

👆맨 위로


유형 검사를 하지마세요.


  • TypeScript 자체가 JS 의 구문적 슈퍼 세트입니다. TS 를 잘 사용하려면 변수와 매개 변수의 유형을 지정하세요.

    안좋은 예:
    
    function travelToTexas(vehicle: Bicycle | Car) {
    if (vehicle instanceof Bicycle) {
    vehicle.pedal(currentLocation, new Location('texas'));
    } else if (vehicle instanceof Car) {
    vehicle.drive(currentLocation, new Location('texas'));
    }
    }
    

좋은 예:

type Vehicle = Bicycle | Car;

function travelToTexas(vehicle: Vehicle) {
    vehicle.move(currentLocation, new Location('texas'));
}

👆맨 위로



여기까지 타입스크립트 클린 코드 가이드, 변수와 함수 편이였습니다.

다음 포스팅은 클래스에 대해 알아보도록 하겠습니다.