본문 바로가기

IT Book

파이브 라인스 오브 코드

반응형

높은 품질의 코드가 유지관리 비용을 절감하고, 오류를 줄이고, 개발자의 만족도를 향산 시킨다.
높은 품질의 코드를 얻는 방법이 리팩터링

스킬

  • 어떤 코드가 잘못됐으며 리팩터링이 필요한지를 파악하는 스킬이 필요하다.
  • 숙련된 프로그래머는 코드 스멜에 대한 지식을 가지고 이를 알아낼 수 있다.
  • 그러나 코드 스멜은(판단과 경험이 필요해서) 경계가 모호하거나 다양하게 해석될수 있으므로 배우기가 쉽지 않다.
  • 그러한 이유로 주니어 개발자들은 코드 스멜을 스킬이라기보다는 육감 같은 것으로 이해한다.
    (코드스멜에 대한 지식)
    문화
  • 리팩터링에 시간을 들이는 것을 권장하는 문화와 절차가 필요하다.
  • 많은 경우 이것은 테스트 주도 개발에서 많이 사용되는 레드-그린-리팩터 순환절차로 구현된다.
  • 그러나 테스트 주도 개발은 매우 어려운 스킬이며, 레드-그린-리팩터는 기존 코드 기반에서 리팩터링을 하는데 큰 도움이 되지 않는다.
    (테스트 주도 개발)
    도구
  • 우리가 하는 작업이 안전하다는 것을 보장할 무언가가 필요하다.
  • 이를 실현하는 가장 일반적인 방법은 자동화된 테스트다.
  • 그러나 이미 언급한 바와 같이 효과적으로 자동 테스트를 수행하는 방법을 배우는 것 자체가 어렵다.
    (자동화된 테스트)

리팩터링이란?

  • 기능을 변경하지 않고 코드를 변경하는 것

해야 하는 이유?

  • 코드를 더 빠르게 만들기 위해
  • 더 작은 코드를 만들기 위해
  • 코드를 더 일반적이거나 재사용 가능하게 하기 위해
  • 코드의 가독성을 높이고 유지보수를 용이하기 위해

좋은코드?

  • 사람이 읽기 쉽고, 유지보수가 용이하며, 의도한 대로 잘 동작하는 코드

리팩터링이란?

  • 기능을 변경하지 않고 코드의 가독성과 유지보수가 쉽도록 코드를 변경하는 것
  1. 프로그래머의 시간은 비싸기 때문에 코드베이스의 가독성이 높이면 새로운 기능을 구현하기 위한 시간을 확보할 수 있다.
  2. 유지보수가 용이해지면 버그가 줄어들고 수정하기 쉬워진다.
  3. 좋은 코드 베이스는 생각하기 편하기 때문이다.

스킬: 무엇을 리팩터링 할 것인가?

  • 코드 스멜 찾기
  • 규칙 (다섯줄 제한)

문화: 리팩터링을 언제 할까?
탐색-명세화-구현-테스트-리팩터링-전달

하지 말아야 될 경우?

  • 한번 실행하고 삭제할 코드
  • 폐기되기 전 유지보수 모드에 있는 코드
  • 엄격한 성능 요구사항이 있는 코드

도구: 안전한 리팩터링 방법

  • 레시피처럼 상세하고 단계별로 구조화된 리팩터링 패턴
  • 버전관리 git
  • 컴파일러

vscode

tsc -w 명령 실행을 지원으로 손수 컴파일하지 않아도 된다.

git reset --hard section-2.1로 초기화

리팩터링 깊게 들여다보기
코드개선

  1. 가독성
  • 의도를 전달하기 위한 코드의 성질
  • 코드가 의도한 대로 작동하는 가정이 있으면 코드가 무슨일을 하는지 파악하기 쉽다는 뜻
  • 코딩 컨벤션을 지정하고 따르기, 주석달기, 변수, 메서드, 클래스 미치 파일 이름 지정, 공백 사용 등
  1. 유지보수성
  • 버그를 고치거나 기능을 추가하기 위해 일부 기능을 변경해야 할 때마다 새 코드를 어디에 놓을지 후보위치를 조사하는 것으로 시작
  • 유지보수성은 얼마나 많은 후보를 조사해야 하는지를 나타내는 표현이다.
  • 취약성의 근원은 일반적으로 전역상태(global state)로 고려한 범위(Scop)를 벗어난 의미다. (내부 변수가 전역 변수를 참조하는 것)
  • 코드에서 상태(조건)을 명시적으로 확인하지 않는 속성을 불변속성(invariant) (이 숫자는 절대 음수일 수 없다.)
  • 변수를 명시적으로 체크해서 불변속성을 제겅함으로써 유지보수를 향상시킬수 있지만 코드가 수행하는 작업이 변경된다.
  • 리팩터링을 더욱 쉽게 볼수 있도록 서로 가깝게 이동시켜 유지보수를성을 향상시켜야 한다.
  1. 코드가 하는 일을 바꾸지 않고 유지보수하기
  • 값을 입력하면 리팩터링 전과 후에 동일한 결과를 얻어야 한다.
  • 의도를 전달함으로써 가독성 향상
  • 불변속성의 범위제한을 통한 유지보수성 향상
  • 범위 밖의 코드에 영향을 주지 않고 위의 내용 수행

속도, 유연성 및 안정성 확보

  1. 상속보다는 컴포지션 사용
  • 대부분의 리팩터링 패턴과 규칙은 구체적으로 객체 컴포지션을 돕기 위한 것.
  • 객체가 내부에 다른 객체의 참조를 가지는 것
  • 컴포지션을 중심으로 만들어진 시스템을 사용하면 다른 방식보다 더 깔끔하게 코드를 결합하고 재사용할 수 있다.
  1. 수정이 아니라 추가로 코드를 변경
  • 컴포지션의 가장 큰 장점은 추가로 변경이 가능하다.
  • 개방-폐쇄 원칙으로 확장에는 열려 있어야 하고 수정에는 닫혀있어야 한다는 의미
  1. 기술부채(technical debt)
  • 배포분도를 높이다보면 한층 더 안정감을 얻게 된다.

요약

다섯줄 제한 규칙

  • 메서드는 다섯 줄 이하여야 한다
  • 두가지 이상의 작업을 수행하는 메서드를 식별하는데 도움이 된다.
  • 리팩터링 패턴인 메서드 추출을 사용해서 긴 메서드를 분해하고 메서드 이름을 주석으로 대신한다.

호출 또는 전달, 한가지만 할 것 규칙

  • 하나의 메서드 내에서 객체에 있는 메서드를 호출하거나 객체를 매개변수로 전달 할 수 있지만, 둘다 해서는 안된다.
  • 여러 수준의 추상화가 섞여 있는 메서드를 식별하는데 도움이 된다.
  • 메서드 추출을 사용해서 서로 다른 수준의 추상화를 분리한다.

메서드 이름은 투명하가 완전해야 하며 이해할 수 있어야 한다. 메서드 추출을 하면 가독성이 향상되도록 매개벼눗의 이름을 바꿀수 있다.

if문은 함수의 시작에만 배치 규칙

  • if를 사용해 조건을 확인하는 경우 한 가지 작업만 수행하므로 메서드가 다른 작업을 수행하지 못하게 한다.
  • 하나 이상의 작업을 수행하는 메서드를 식별하는데 도움이 된다.
  • if문을 분리하기 위해 메서드 추출을 사용한다.

타입 코드 처리하기

  1. if문에서 else사용하지 말것
    규칙 적용: 열거형에서 인터페이스로 바꾸는것
enum Input {
    RIGHT, LEFT, UP, DOWN
}
interface Input2 {  
isRight(): boolean;  
isLeft(): boolean;  
isUp(): boolean;  
isDown(): boolean;  
}
class Right implements Input2 {  
isRight() { return true; }  
isLeft() { return false; }  
isUp() { return false; }  
isDown() { return flase; }  
}
function handleInput(input: Input2) {  
if(input.isLeft()) {  
moveHorizontal(-1);  
} else if (input.isRight()) {  
moveHorizontal(1);  
} else if (input.isUp()) {  
moveVertical(-1);  
} else if (input.isDown()) {  
moveVertical(1);  
}  
}
new Right();  
new Left();  
new Up();  
new Down();
window.addEventListener('keydown', e => {  
if(e.key === LEFT\_KEY || e.key === "a") {  
inputs.push(new Left());  
} ...  
});

리팩터링 패턴: 클래스로 타입 코드 대체

  • 열거형을 인터페이스로 변환하고 열거형의 값들은 클래스가 된다.
  • 각 값에 속성을 추가하고 해당 특정 값과 관련된 기능을 특성에 맞게 만들수 있다.
  • 열거형의 값을 클래스로 변환할때는 다른 열거 값을 고려하지 않고 해당 값과 관련된 기능을 그룹화할 수 있다.

리팩터링 패턴: 클래스로 코드 이관

  • 기능을 클래스로 옮기기 때문에 클래스로 타입 코드 대체 패턴의 자연스러운 연장선에 있다.
  • if문이 제거되고 기능이 데이터에 더 가까이 이동한다.
  • 특정값과 연결된 기능이 값에 해당하는 클래스로 이동하기 때문에 이는 불변속성을 지역화하는데 도움이 된다.

리팩터링 패턴: 메서드의 인라인화
코드추가(클래스를 지원하기 위해)와 코드 제거의 중요한 주제

  • 이 리팩터링은 후자를 지원한다.
  • 즉 프로그램에서 더 이상 가독성에 도움이 되지 않는 메서드를 제거한다.
  • 메서드에서 이를 호출하는 모든 곳으로 코드를 옮기는 것이다.
  • 메서드가 사용되지 않으므로 안전하게 삭제할수 있다.
  • 메서드 인라인화 리팩터링과 메서드를 인라인으로 사용하는것은 다르다.

규칙: switch를 사용하지 말것

  • default 케이스가 없고 모든 case에 반환값이 있는 경우가 아니라면 switch를 사용하지 마십시오.

규칙: 인터페이스에서만 상속받을 것

  • 상속은 오직 인터페이스를 통해서만 받는다.

요약

  • if문에서 else를 사용하지 말것. 그리고 switch를 사용하지 말것 규칙에 따르면 else 또는 switch는 프로그램의 가장자리에만 있어야 한다.
  • else와 switch 모두 낮은 수준의 제어흐름 연산자이다.
  • 애플리케이션의 핵심에서는 클래스로 타입 코드 대체 및 클래스로의 코드 이관 리팩터링 패턴을 사용해서 switch와 연속된 else if 구문을 높은 수준의 클래스와 메서드로 대체해야 한다.
  • 지나치게 일반화된 메서드는 리팩터링을 방해할 수 있다. 이런 경우 불필요한 일반성을 제거하기 위해 메서드 전문화 리팩터링 패턴을 사용할 수 있다.
  • 인터페이스에서만 상속받을 것 규칙은 추상 클래스와 클래스 상속을 사용해 코드를 재사용하는 것을 방지한다.
  • 이러한 유형의 상속은 불필요하게 긴밀한 커플링을 발생시키기 때문이다.
  • 리팩터링 후 정리를 위한 두가지 리팩터링 패턴으로 메서드의 인라인화, 삭제후 컴파일하기로 더 이상 가독성에 도움이 되지 않는 메서드를 제거할 수 있다.

유사한 코드 융합하기

리팩터링 패턴: 유사 클래스 통합

  • 일련의 상수 메서드를 공통으로 가진 두개 이상의 클래스에서 이 일련의 상수 메서드가 클래스에 따라 다른값을 반환할때마다 이 리팩터링 패턴을 사용해서 클래스를 통합할수 있다.
    일련의 상수 메서드의 집합을 기준이라고한다. 일련의 상수 메서드가 두개일때 두개의 접점을 가진 기준이라고 한다.

단순한 조건 통합하기

리팩터링 패턴: if문 결합

  • 동일한 연속적인 if문을 결합해서 중복을 제거한다.

복잡한 조건 통합하기

산술규칙 사용

  • 결합법칙
  • 교환법칙
  • 분배법칙

클래스 간의 코드 통합

UML

"uses a"
- - - -> 의존
------> 연관

"is a"
------▷ 상속
- - - -▷ 구현

"has a"
◇------ 집합
◆------ 컴포지션

리팩터링 패턴: 전략패턴의 도입

  • 다른클래스를 인스턴스화해서 변형을 도입하는 개념을 전략 패턴이라고 한다.
  • 전략 패턴의 변형은 늦은 바인딩의 궁극적인 형태이다.
  • 런타임에 전략 패턴을 사용하면 코드에 사용자 정의 클래스를 적재하고 이를 제어 흐름에 원할하게 통합할수 있다.
  • 코드를 다시 컴파일할 필요도 없다.

도입상황

  1. 코드에 변형을 도입하고 싶어서 리팩터링을 수행하는 경우 (인터페이스가 있어야 한다.)
  2. 떨어지는 성질을 코드화했던 상황에서 바로 변형의 추가가 필요하다고 예상하지 않았을 때 이다.
    (단지 클래스 간의 동작을 통합하려는 경우)

규칙: 구현체가 하나뿐인 인터페이스를 만들지 말것

  • 구현체가 하나뿐인 인터페이스를 사용하지 마십시오.
  • 단일 구현체를 가진 인터페이스가 있으면 안된다고 명시한다.
  • 이런 인터페이스는 종종 '항상 인터페이스로 코딩하라'는 코딩 학습시 듣게 되는 조언에서 비롯됐다.
  • 구현 클래스가 하나밖에 없는 인터페이스는 가독성에 도움이 되지 않는다.
  • 구현 클래스를 수정하려는 경우 인터페이스를 수정해야 해서 오버헤드를 발생시킨다.
  • 메서드 전문화와 유사한다. 구현 클래스가 하나만 있는 인터페이스는 도움이 되지 않는 일반화의 한 형태이기 때문이다.

리팩터링 패턴: 구현에서 인터페이스 추출

  • 인터페이스를 만드는 것을 필요할때 (변형을 도입하고 싶을때)까지 연기할 수 있어 유용하다.

유사함수, 유사코드 통합하기

요약

  • 모아야할 코드가 있을 때 우리는 그것을 통합해야 한다.
  • 유사클래스 통합, if문 결합을 사용해서 클래스를 통합하고 전략 패턴을 도입해서 메서드를 통합할 수 있다.
  • 순수조건사용 규칙은 조건에 부수적인 동작이 없어야 한다고 명시하고 있다.
  • 부수적인 동작이 없는 경우 조건부 산술을 사용할 수 있기 때문이다. 또한 부수적인 동작을 조건과 분리하기 위해 캐시를 사용하는 방법
  • UML 클래스 다이어그램은 일반적으로 코드베이스에 대한 특정 아키텍처의 변경을 설명하기 위해 사용된다.
  • 구현클래스가 하나뿐인 인터페이스는 불필요한 일반화의 한 형태이다.
  • 구현체가 하나뿐인 인터페이스를 만들지 말것 규칙에는 이런 항목이 없어야 한다고 명시한다.
  • 대신 구현에서 인터페이스 추출 리팩터링 패턴을 사용해 인터페이스를 나중에 도입해야 한다.

데이터 보호

규칙: getter, setter 사용하지 말것

  • boolean이 아닌 필드에 setter, getter 사용하지 마십시오.

리팩터링 패턴: getter, setter 제거하기

  • 코드를 데이터에 여러번 더 가깝게 이동함으로써 불변속성을 지역화했다.
  • getter 대신 유사한 함수들을 많이 도입하게 된다.

간단한 데이터 캡슐화하기

규칙: 공통접사를 사용하지말것

  • 코드에 공통 접두사나 접미사가 있는 메서드나 변수가 없어야 한다.
  • 사용자 이름의 경우 username, 타이머를 시작할때의 동작하는 경우 startTimer와 같이 해당 컨텍스트를 암시하기 위해 메서드나 변수에 접미사나 접두사를 붙일때가 많다.
  • 이것은 컨텍스트를 잘 전달하기 위한 것이다.
  • 코드를 더 읽기 쉽게 만들수 있지만 여러 요소가 동일한 접사를 가질때는 그 요소들의 긴밀성을 나타내기도 한다.
  • 이런 구조를 전달하기에 더 좋은 방법으로 클래스가 있다.
  • 클래스를 사용해서 이런 메서드와 변수를 그룹화하는 장점은 외부 인터페이스를 안전하게 제어할 수 있다.

리팩터링 패턴: 데이터 캡슐화

  • 변수와 메서드를 캡슐화해서 접근할수 있는 지점을 제한하고 구조를 명확하게 만들 수 있다.
  • 메서드를 캡슐화하면 이름을 단순하게 만들고 응집력을 더 명확하게 하는데 도움이 된다.
  • 이것은 더 좋은 클래스로 이어진다.

복잡한 데이터 캡슐화

  • 데이터 캡슐화를 적용

순서에 존재하는 불변속성 제거하기

  • 객체지향 프로그래밍에서 초기화를 위한 다른 메커니즘으로 생성자가 있다.

리팩터링 패턴: 순서 강제화

  • 생성자가 항상 객체의 메서드보다 먼저 호출되어야 한다.
  • 이 속성을 활용해서 작업이 특정 순서로 발생하게 할 수 있다.

열거형을 제거하는 또 다른 방법

  • 비공개 생성자를 통한 열거
  • 숫자를 클래스에 다시 매핑하기

요약

  • 캡슐화를 시행하러면 데이터의 노출을 피해야 한다. getter, setter를 사용하지 말것 규칙은 getter, setter를 통해 간접적으로 비공개 필드를 노출해서는 안된다는 의미다.
  • getter, setter 제거하기 리팩터링 패턴을 사용해서 getter, setter를 제거할 수 있다.
  • 공통 접사를 사용하지 말것 규칙에 따르면 공통 접두사 접미사가 있는 메서드와 변수가 있는 경우 한 클래스에 함께 있어야 한다. 이를 위해 데이터 캡슐화 리팩터링 패턴을 사용할 수 있다.
  • 클래스를 이용한 순서 강제화 리팩터링 패턴을 사용하면 컴파일러가 실행하는 순서를 강제할 수 있도록 해서 순서 불변속성을 제거할수 있다. 열거형을 처리하는 또 다른 방법은 비공개 생성자가 있는 클래스를 사용하는 것이다. 그러면 열거형과 스위치 문을 추가적으로 제거할수 있다.

배운 내용을 현실세계에서 적용하기

요약
최신컴파일러의 일반적인 장점과 약점

  • 컴파일러의 도달성 분석을 이용해서 switch가 모든 case를 커버하고 있는지 확인
  • 변수에 값이 있는지 확인하려면 확정 할당을 사용
  • 접근제어(private, public)를 사용해서 민감한 불변속성이 있는 메서드를 보호
  • 변수를 역참조하기 전에 변수가 null이 아닌지 확인한다.
  • 나누기 전에 나누는 숫자가 0이 아닌지 확인한다.
  • 작업이 오버플로 또는 언더플로되지 않는지, 또 BigInteger를 사용하지 않는지 확인한다.
  • 아웃오브바운드 에러를 피하기 위해 데이터 구조의 전체 데이터를 검사하거나 확정 할당을 사용한다.
  • 더 높은 수준의 구조를 사용해서 무한 루프를 방지한다.
  • 여러 스레드가 변경 가능한 데이터를 공유하지 않도록해서 스레드 문제를 방지한다.

컴파일과 싸우는 대신 컴파일러를 사용해서 더 높은 수준의 안정성을 도달하는 방법

  • 리팩터링 시 컴파일러 오류를 TODO 리스트로 이용한다.
  • 컴파일러를 이용해서 순서 불변속성을 강제한다.
  • 컴파일러를 이용해서 사용하지 않는 코드를 찾아낸다.
  • 형 변환 또는 동적/런타임 타입을 사용하지 않는다.
  • 기본값과 클래스에서의 상속 또는 확인되지 않은 예외를 사용하지 않는다.
  • 캡슐화가 깨지는 것을 방지하려면 private 필드 대신 this를 전달해서 메서드를 사용한다.

컴파일러를 신뢰하고 컴파일러의 출력 결과에 주의를 기울이며, 오염되지 않는 코드베이스를 유ㅜ지해서 경로 피로를 피하라.

코드가 작동할지 예측할때 컴파일러에 의존하라.

요약
주석은 개발 중에는 유용할 수 있지만 배포 전에는 제거해야 한다.
주석 다섯가지 유형

  • 오래된 주석은 버그를 유발할수 있으므로 삭제
  • 버전관리 시스템이 있으므로 주석 처리된 코드는 삭제
  • 불필요한 주석은 가독성을 더하지 않기 때문에 삭제
  • 메서드 이름이 될 수 있는 주석은 메서드명으로 대신
  • 로컬이 아닌 불변속성을 문서화하는 주석은 코드 또는 자동 테스트로 변환해야한다. 여의치 않으면 주석을 유지해도 좋다.

파이브 라인스 오브 코드
- 크리스찬 클라우젠 저/김성원 역

반응형