News Feed

더 좋은 코드를 위한 11가지 규칙

컨텐츠 정보

  • 조회 742

본문

30년 동안 소프트웨어를 개발하고 소프트웨어 프로젝트를 관리하다 보면 많건 적건 좋은 코드를 쓰기 위한 방법을 터득하게 된다. 그 시간 동안 필자가 습득한 코딩에 관한 몇 가지 요령을 소개한다.

간단할수록 좋다

말할 필요도 없는 기본임에도 불구하고 KISS(“Keep It Simple, Stupid”) 원칙을 무시하는 경우는 놀라울 정도로 많다. 개발자는 새로운 방식으로 일하기를 것을 좋아하는 것 같다. “단순히 객체를 생성하고 사용하는” 방법을 놔두고 굳이 람다를 이리저리 돌려가며 작업한다. 간단한 표준 데이터 구조를 사용하는 대신 자기만의 첨단 맞춤형 솔루션을 구축한다. 추상화 계층이 더 추가된다고 한들 무슨 문제인가라고 생각한다.

멋지고 복잡한 새로운 언어 기능이 나왔다고 해서 그 기능을 꼭 사용해야 하는 것은 아니다. 복잡성은 단순함의 대척점이다. 복잡한 코드를 피하는 방법은 복잡하게 쓰지 않는 것이다. 간단하게 작성하라.

명확할 것

개발자는 의도치 않게 자신의 코드를 읽게 어렵게 만드는 경우가 많다. TxMgrObj라는 변수의 의미를 안다고 해서 다음 개발자도 당연히 알 것이라고 전제해서는 안 된다. 그냥 TransactionManager라는 명확한 이름을 사용하지 않는 이유가 무엇일까?

다음은 눈이 아픈 코드다.

const txMgrObj = getTxMgr();txMgrObj.process();

그러나 다음 코드는 눈이 즐겁다.

const transactionManager = getTransactionManager();transactionManager.process();

누군가가 “그 많은 문자를 언제 다 치고 있냐”라고 말한다면 필자는 “게으름은 문제를 해결하는 방법이 아니다”라고 답하겠다. 키보드를 몇 번 덜 누르면 시간이 절약되고 일이 편해질 것이라는 생각은 어리석고 터무니없다.

명확하게 쓴다는 것은 코드의 줄 수가 늘어나는 것을 의미한다. 예를 들어 항상 설명 변수를 사용하라. 4개의 서로 다른 부울 조건이 있는 if 문 대신 4개의 부울 변수와 이 4개 변수 사이의 관계를 정의하는 다섯 번째 변수를 두고 이를 if 문에서 사용하면 어떨까? (더 좋은 방법은 if 문을 아예 사용하지 않는 것이다. 아래 참조.)

물론 이름 붙이기는 힘든 일이지만 길고 좋은 이름은 많은 것을 설명할 수 있다. NumberOfSpacesAllowedOnPrinterLine이라는 변수를 사용하기를 두려워하면 안 된다. 필자가 장담컨대, 누구도 이 변수 이름을 두고 불평하지 않을 것이다.

데메테르의 법칙 따르기

마트 계산대에 가서 결제할 때는 지갑에서 카드를 꺼내 계산한다. 지갑을 통째로 주는 경우는 없다.

데메테르의 법칙은 상호작용을 절대적 최소한으로 제한하는 것이다. 메서드에 트랜잭션 번호가 필요하다면 트랜잭션 번호를 전달해야지, 트랜잭션 번호를 갖고 있는 클래스 또는 트랜잭션 번호가 포함된 쿼리 객체를 전달하지 말라는 것이다.

0, 1 또는 N에 대한 코드

과거 최대 6개 요소로 하드코딩된 세금 배열이 있는 시스템에서 작업한 적이 있다. 전체 시스템이 이 제한을 고려해 설계됐다. 필자는 코드를 작성한 당시 개발자가 하나의 주문에 대해 여섯 가지 세금도 비합리적으로 많지만 “그냥 만일의 경우를 대비해서” 여섯 개로 설정했을 것이라고 확신한다. 이후 무슨 일이 일어났는지는 굳이 말할 필요도 없을 것이다.

주문을 삭제하기 전에 주문에 아무것도 없는지 확인해야 하는 경우가 종종 있다. 어느 이벤트에 대해 이벤트 핸들러가 하나만 있는지 확인해야 하는 경우도 있고, 몇 개의 이벤트 핸들러라도 허용해야 하는 경우도 있다.

그게 무엇이든, 가령 4개 이상은 시스템에 들어갈 수 없다고 결정한다면 그 전에 반드시 확인해야 한다. 이 규칙의 필연적인 결과는 다음 주장으로 이어진다.

아무것도 하드코딩하지 말 것

당연한 말처럼 들리지만, 의외로 하드코딩을 좋아하는 개발자도 있다. 다음과 같은 믿기 어려운 경우를 흔히 볼 수 있다.

someString.PadLeft(13);

13이라고? 14도 아니고 12도 아닌 13인 이유는 뭘까? 값의 의미를 설명하는 상수를 사용한다면 어떨까?

그렇게 생각하지 않을 수도 있지만 클래스 내에 객체를 생성한다는 것은 곧 해당 클래스와 구현을 하드코딩하는 것이다.

class SimpleEncryptor {  public encrypt(plainText: string): string {    const weakEncryption = new WeakEncryptionAlgorithm();    return weakEncryption.encrypt(plainText);  }}

그렇다면 암호화 알고리즘을 변경하려는 경우 어떻게 해야 할까?

종속성 주입을 사용하면 원하는 알고리즘을 사용할 수 있다.

interface IEncryptionAlgorithm {  encrypt(plainText: string): string;}class SimpleEncryptor {  public encrypt(plainText: string, encryptionAlgorithm: IEncryptionAlgorithm): string {    return encryptionAlgorithm.encrypt(plainText);  }}

‘오버 엔지니어링’이 적절한 엔지니어링이다

필자도 오버 엔지니어링 개념이 무엇인지는 안다. 조금 전의 간단히 하라는 말과는 모순 같을 수도 있다. 그러나 “올바른 방법”이 오버 엔지니어링처럼 보이는 경우도 종종 있다.

자신이 오버 엔지니어링하고 있다는 생각이 들 때마다 잠시 멈추고 이 말을 생각해 보라. 오버 엔지니어링이 아닐 수도 있다. 인터페이스를 만들고 거기에 맞춰 코딩하는 것은 마치 오버 엔지니어링같다.

물론 둘은 구분하기가 까다롭다. 그러나 필요성을 확신하는 부분에서 계획을 수립하는 것은 잘못이 아니다. 여기에서 다음 원칙이 이어진다.

필요할 때도 있다

필자는 YAGNI(“You Aren’t Gonna Need It”) 원칙을 좀처럼 이해할 수 없다. 결국 필요하게 되는 상황이 너무 많기 때문이다. 그리고 필요하게 된 시점에는 앞서 “필요하지 않을 것”이라고 생각했던 기능을 구현하기가 너무 어려워져서 미리 준비하지 않았음을 후회하게 된다.

뭔가를 하드코딩했을 수도 있고(유연성이 필요하지 없을 것이라는 생각에) 일곱 가지 세금 또는 다른 암호화 알고리즘의 필요성에 대비한 계획을 전혀 하지 않았을 수도 있다. “언젠가는 더 많은 요소를 다뤄야 하는 상황이 올 것”이라는 생각으로 새로운 것이 등장할 때(필연적) 쉽게 변경이 가능하도록 코딩하는 방식에는 전혀 문제가 없다는 것이 필자의 생각이다.

명령줄을 첫 번째 사용자 인터페이스로 만들기

모든 비즈니스 로직, 그리고 인터페이스 외의 코드는 명령줄 애플리케이션을 통해 액세스할 수 있어야 한다. 명령줄에서 코드를 실행할 수 없고 GUI나 기타 인터페이스가 있어야 실행이 가능하다면 코드가 부적절하게 얽히고 묶인 상황이다.

필자는 모든 형편없는 코드의 중심에는 비즈니스 로직을 사용자 인터페이스에 묶는 행위가 존재한다는 사실을 깨닫고, 그 순간부터 이 접근 방식을 취하기 시작했다. 이 둘은 기름과 식초처럼 다뤄야 함에도 섞이는 경우가 너무 많다. 관심사를 분리하고, 명령줄 애플리케이션으로 코드를 실행해 이를 입증하라.

if 문에 주의, 매우 주의

필자는 if 문을 사용할 때마다 잠시 작업을 멈추고 “이것을 두 개의 루틴으로 만들어야 할지”, 아니면 더 나아가 “완전히 새로운 클래스로 만들어야 할지”를 생각해야 한다고 굳게 믿는다.

필자가 발견하는 대다수의 엉터리 스파게티 코드와 비현실적으로 비대한 루틴은 깊게 파묻힌 많은 수의 if 문에 따른 결과다. 무슨 말인지 알 것이다. 문자 그대로, 거대한 수천 줄 길이의 프로시저 하나로 47가지 상황을 모두 처리하는 루틴이다.

if 문은 아래에 소개할 마지막 원칙의 위반으로 이어지는 경우가 많으므로 각별히 주의해야 한다.

모든 것은 한 가지만 해야

아마 소프트웨어 개발자가 알아야 할 가장 중요한 규칙일 것이다. 모든 것은 한 가지 일만 해야 한다. 여기서 모든 것이란 모든 코드 줄, 모든 루틴, 모든 클래스, 말 그대로 모든 것이다.

라이언 싱어가 말했듯이, 하나로 두 가지 일을 하려다 보면 발생하는 비극에는 끝이 없다. 어느 하나로 26가지 일을 하는 것이 얼마나 끔찍한지 생각해보라.

이름에 “and”가 포함된 프로시저를 볼 때마다 비명을 지르고 싶다. ProcessAndStoreOrder()? 용서할 수 없다!

메서드 추출(Extract Method) 리팩터링을 사용할 기회가 보인다면 사용하라. “프로시저를 얼마나 오래 유지해야 하는가”를 생각해본 적이 있다면 답은 간단하다. 메서드 추출 툴을 사용해서 더 이상 리팩터링을 적용할 수 없을 때까지 계속 더 짧게 만들면 된다. 이렇게 하면 곧 코드는 단 한 가지 일만 하는 개별 메서드의 모음으로 바뀌게 된다.

물론 그 결과로 무수히 많은 작은 함수와 프로시저가 생성된다. 하지만 캡슐화란 결국 함수와 프로시저, 데이터를 한데 모으는 것이 아닌가?

복잡성은 해롭다

앞의 모든 내용을 하나의 규칙으로 압축한다면 ‘복잡성은 비극으로 이어진다’는 것이다. 좋은 코드를 쓴다는 것은 곧 간단하고 명확하며 “지루한” 코드, 즉 이해하는 데 필요한 인지적 노력을 최소화하는 코드를 쓰는 것을 의미한다.

이 글을 읽고 마지막으로 한 가지를 새긴다면 존 우즈의 조언이다 “코딩을 할 때는 항상 내가 어디에 살고 있는지 아는 폭력적인 사이코패스가 내 코드를 유지관리할 것이라는 생각으로 코딩하라.”
dl-itworldkorea@foundryco.com

관련자료

댓글 0
등록된 댓글이 없습니다.
Member Rank