지식조각모음
2장 리팩터링 깊게 들여다보기 본문
이 장의 주제
가독성을 통한 의도 전달
유지보수성 개선을 위한 불변속성 지역화
개발 속도 향상을 위한, 추가를 통해 변경 가능하게 만들기
리팩터링의 일상 업무화
리팩터링이란 코드가 하는 일을 변경하지 않고 더 나은 코드를 만드는 것입니다 (p. 15)
가독성 및 유지보수성 향상
가독성
- 의도를 전달하기 위한 코드의 성질
- 코드가 무슨 일을 하는지 그 의도를 파악하기 쉬운 정도
예를 들어 아래 같은 코드는 다음과 같은 이유로 읽기가 어렵다
function checkValue(str: boolean) {
// 값 체크
if (str !== false)
// 반환
return true;
else; // 그렇지 않으면
return str;
}
- 바람직하지 않은 매서드명
- 매개변수 타입과 매개변수명이 불일치
- 무의미하고 이름만 반복하는 주석
- 이중 부정(str !== false)
- else 옆에 놓치기 쉬운 세미콜론
- return str; 처럼 오해의 소지가 있는(else에 속해 있는 내용처럼 보인다) 들여쓰기
위 코드는 아래처럼 바꾸면 이해하기 쉬워진다.
function isTrue(bool: boolean) {
return bool;
}
유지보수성
- 기능 추가, 변경, 오류 수정 등을 위해 context를 조사해야 한다.
- 얼마나 많은 context를 조사해야 하는지 나타내는 표현
작업을 위해 들여다봐야할 코드가 많아질수록 작업 시간이 늘어나고 오류가 발생할 가능성이 높아진다. 또한 어떤 것을 수정했는데 관련 없는 부분에서 문제가 발생하는 경우가 있을 수 있다. 이럴수록 작업은 어려워진다.
취약성
위와 같이 어떤 것을 수정했는데 관련 없는 부분에서 문제가 발생하는 경우를 취약하다고 한다.
그 이유는 일반적으로 전역상태 때문이다. 우리가 의도한 것이 그 범위를 벗어난 경우에 취약점이 나타난다. 코드에서 상태(조건)를 명시적으로 확인하지 않는, 항상 유효한 속성을 불변속성이라고 한다. 예를 들어 "이 숫자는 절대 음수일 수 없습니다" 같은 것인데, 시스템이 변경되거나 사람의 실수로 불변속성이 유효한 상태로 유지되는 것은 거의 불가능하다. 그래서 변수를 명시적으로 체크해서 불변속성을 제거해야 유지보수성을 향상시킬 수 있다.
범위(영향력)
가독성과 유지보수성을 향상시키기 위해서 코드의 개선이 필요하다. 그럼 코드를 얼만큼 개선해야 할까? 많은 코드를 개선 대상에 포함하면 좋겠지만 그러면 같이 일하는 다른 사람의 작업에 방해가 될수 있다. 또는 병합하다가 충돌이 발생할 수 있다. 또 위에서 말한 불변속성을 제거하려다가 코드가 수행하는 내용이 바뀔수도 있다.
따라서 리팩터링의 범위는 코드에 영향을 주지 않으면서 가독성과 유지보수성을 향상 시키는 정도로 결정해야 한다.
속도, 유연성 및 안정성 확보
범위가 제한되지 않은 불변속성을 유지보수하기 어렵다. 이를 위해 상속보다는 컴포지션을 사용하여 객체가 내부에 다른 객체의 참조를 가지도록 한다.
interface Bird {
hasBeak(): boolean;
canFly(): boolean;
}
class CommonBird implements Bird {
hasBeak() { return true; }
canFly() { return true; }
}
// 상속
class PenguinA extends CommonBird {
canFly() { return false; }
}
// 컴포지션
class PenguinB implements CommonBird {
private bird = new CommonBird();
hasBeak() { return bird.hasBeak(); }
canFly() { return false; }
}
Bird에 canSwim이라는 메서드를 추가하고 펭귄은 수영할 수 있지만 일반적으로 다른 새들은 수영할수 없다고 가정한다.
interface Bird {
hasBeak(): boolean;
canFly(): boolean;
canSwim(): boolean; // 기능 추가
}
class CommonBird implements Bird {
hasBeak() { return true; }
canFly() { return true; }
canSwim() { return false; } // 추가된 기능 구현
}
// 상속
class PenguinA extends CommonBird {
canFly() { return false; }
// 수정하지 않아도 문제 없음
}
// 컴포지션
class PenguinB implements CommonBird {
private bird = new CommonBird();
hasBeak() { return bird.hasBeak(); }
canFly() { return false; }
canSwim() { return true; } // 구현하지 않으면 에러가 발생한다
}
CommonBird에만 canSwim 메서드를 구현한다면 PenguinA는 아무 수정이 없어도 문제가 없지만 PenguinB에서는 컴파일 에러가 발생한다. 따라서 수동으로 canSwim을 재정의해야 한다. 이것만 보면 상속이 더 편해보인다.
그렇지만 실제로 문제가 발생하는 것은 PenguinA이다. 의도와는 다르게 펭귄이 수영을 못하게 되었기 때문이다.
컴포지션을 사용하면 추가(addition)으로 변경이 가능하다. 기존 기능에 영향을 주지 않고 기능을 추가하거나 변경할수 있다. 덕분에 OCP를 준수할수 있다.
기존 기능에 영향을 주지 않는다는 것이 명확하면 개발 속도도 빨라지고 안정성이 향상된다.
🧐 추가로 공부할 것
불변속성, 컴포지션
'책 > Five Lines of Code' 카테고리의 다른 글
| 8장 주석 자제하기 (0) | 2023.09.04 |
|---|---|
| 4장 타입 코드 처리하기 (0) | 2023.08.17 |
| 3장 긴 코드 조각내기 (0) | 2023.08.05 |
| 1장 리팩터링 리팩터링하기 (0) | 2023.08.01 |
| 스터디 시작 (0) | 2023.07.30 |