TIL | WIL

2/17 금 (Rule of Three 중복 제거 리팩토링, 생생한 예제 코드가 여기있다) TIL

깊은바다거북 2023. 2. 18. 00:50

(막간 보충 공부중)

오늘 한 것:

  • 프로그래머스 Lv0 점의 위치 구하기
https://school.programmers.co.kr/learn/courses/30/lessons/120841
  • 프로그래머스 Lv0 배열 뒤집기
https://school.programmers.co.kr/learn/courses/30/lessons/120821
  • 8시 문제 풀이 스터디
  • Nest.js로 간단 게시판 만들기 코드 재작성 (2회차) ⇒ 오오오 이해가 된다 된다..!

공부 자료 후보

TypeORM 이슈

entities: [Article]

여기 참조하라고: https://docs.nestjs.com/recipes/sql-typeorm#getting-started

(아마존 AWS 무슨무슨 인스턴스 사용하고 있는지 전체를 보고 싶을 때: )

“청구서”를 확인해 힌트를 얻거나 - https://us-east-1.console.aws.amazon.com/billing/home?region=us-east-1#/bills?year=2023&month=2

“Free tier” 항목을 확인해 힌트를 얻는다 - https://us-east-1.console.aws.amazon.com/billing/home?region=us-east-1#/freetier


“Rule of Three”, 중복 제거 리팩토링의 황금률

중복을 제거하기 위한 리팩토링을 실시하기 위한 최적의 타이밍은 반복되는 코드를 3번 작성하게 됐을 때이다.

예시로, Mars Rover 라는 코딩 문제 중 ‘화성 탐사 무인기를 오른쪽으로 방향 전환 시켜라’라는 기능을 테스트 및 구현중이라고 하자.

소스 코드 리팩토링 예시

리팩토링 이전

// src/mars-rover-kata.js
function go(rover, instruction) {
    if (rover.facing === 'N') {
        rover.facing = 'E'
    } else if (rover.facing === 'E') {
        rover.facing = 'S'
    } else {
        rover.facing = 'W';
    }
    return { ...rover };
}

module.exports.go = go;

이렇게 if문으로 3번 반복되어 쓰여지는 타이밍에 리팩토링을 하도록 한다.

이후

function go(rover, instruction) {
    const compass = ['N', 'E', 'S', 'W'];
    rover.facing = compass[(compass.indexOf(rover.facing) + 1) % compass.length]
    return { ...rover };
}

module.exports.go = go;

아니면 더 간단하게 이렇게도

function go(rover, instruction) {
    const compass = ['N', 'E', 'S', 'W'];
    return {...rover, facing: compass[(compass.indexOf(rover.facing) + 1) % compass.length]}
}

module.exports.go = go;

테스트 코드 리팩토링 예시

짝이 되는 테스트 코드도 리팩토링의 대상이다!

이전

// src/mars-rover-kata.test.js
const { go } = require('./mars-rover-kata')

describe('Mars Rover', () => {
    it('turns right from N to E', () => {
        let rover = {facing: 'N'}
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('E');
    });

    it('turns right from E to S', () => {
        let rover = {facing: 'E'};
        rover = go(rover, 'R');
        expect(rover.facing).toEqual('S');
    });

    it('turns right from S to W', () => {
        let rover = {facing: 'S'}
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('W');
    });

    it('turns right from W to N', () => {
        let rover = {facing: 'W'}
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('N');
    });
})

// => 
PASS  src/mars-rover-kata.test.js
  Mars Rover
		√ turns right from N to E (1 ms)
		√ turns right from E to S
		√ turns right from S to W
		√ turns right from W to N (1 ms)

테스트 케이스(it) 마다 같은 패턴이 반복되고 있다. 리팩토링 해보자.

좋지 않은 리팩토링 (나의 시도):

// src/mars-rover-kata.test.js
const { go } = require('./mars-rover-kata')

describe('Mars Rover', () => {
    it('turns right properly from any of the starting direction', () => {
        let rover = {facing: 'N'}
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('E');
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('S');
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('W');
        rover = go(rover, 'R')
        expect(rover.facing).toEqual('N');
    });
})

// => 
PASS  src/mars-rover-kata.test.js
  Mars Rover
		√ turns right properly from any of the starting direction (1 ms)

한 테스트 케이스 안에 여러 테스트 상황을 뭉뚱그려 넣는 것은 좋지 않다고 할 수 있다.

제대로 된 리팩토링:

// src/mars-rover-kata.test.js
const { go } = require('./mars-rover-kata')

describe('Mars Rover', () => {
    [
        { startFacing: 'N', endsFacing: 'E' },
        { startFacing: 'E', endsFacing: 'S' },
        { startFacing: 'S', endsFacing: 'W' },
        { startFacing: 'W', endsFacing: 'N' },
    ].forEach(({startFacing, endsFacing}) => {
        it(`turns right from ${startFacing} to ${endsFacing}`, () => {
            let rover = {facing: startFacing};
            rover = go(rover, 'R');
            expect(rover.facing).toEqual(endsFacing);
        });
    });
})

// => 
PASS  src/mars-rover-kata.test.js
  Mars Rover
		√ turns right from N to E (1 ms)
		√ turns right from E to S
		√ turns right from S to W
		√ turns right from W to N (1 ms)

(덜 영근) 궁금증들

1. 클래스가 타입으로 사용될 수 있었나?

// 04.3_nest_practice_with_typeorm/src/app.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
...
let app: TestingModule;
app = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService],
    }).compile();

⇒ Test도 TestingModule도 둘 다 class인데, 하나는 타입 선언으로 쓰였다.

  • 클래스를 타입으로 사용할 수 있는가?? 분명 타입 선언 자리에 올 수 있는 것(’선언 타입군’) 여기에 정리하기로는 “Interface로 선언한 클래스”만 타입 선언 자리에 올 수 있다고 했는데, 저 TestingModule은 아무리 봐도 그냥 클래스이다.
    // @nestjs/testing/testing-module.d.ts
    export declare class TestingModule extends NestApplicationContext {

2. module.exports = 과 그냥 export class …의 차이

3. 클래스에서 declare, protected, readonly, private의 사용 위치

export declare class ValidationPipe implements PipeTransform<any> {
    protected isTransformEnabled: boolean;
		...
}

4. ES6 그 이전과 이후 - 수집중

여기저기서 나타나는 흔적들:

// tscongif.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "esModuleInterop": true
  }
}

"esModuleInterop": true : “ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 한다.”


Uploaded by N2T