2018.04.16

좋은 코드를 작성하고 있다는 징후 11가지

Andrew C. Oliver | InfoWorld
어떤 개발언어를 사용하든 어떤 기술을 사용하든 자신이 만드는 코드를 이런 형용사로 설명할 수 있다면, 좋은 코드는 따라오기 마련이다.

빌 소러는 윤리적 관점에서 스스로 부끄러움을 느낀 코드에 대한 좋은 글을 미디엄(Medium)에 올린 적이 있다. 그러나 기술적인 측면에서도 소프트웨어를 부끄러워해야 이유는 많다. 부끄러워하지 않을 소프트웨어를 나타내는 11가지 단어를 살펴보자.



사용하는 언어 또는 기술 스택이 무엇이든 코드를 다음 단어로 설명할 수 있다면 좋은 코드일 가능성이 높다. 자신의 코드에 몇 개나 적용되는지 확인해 보라.

1. 디버그 가능(Debuggable)
대부분의 현대 런타임에서는 일종의 디버거를 연결할 수 있다. Node.js조차 비주얼 스튜디오로 디버그가 가능하다. 언젠가 무슨 일이 벌어지는지 알아내야 할 때 디버거를 연결해야 한다는 생각을 갖고 코드를 써야 한다.

그 의미를 정확히 설명하기는 어렵지만 이렇게 하면 때로는 데이터의 구조를 설계하는 데 영향을 미치기도 한다. 구조를 파나갈 필요가 없도록 더 많은 임시 변수를 사용할 수도 있다. 다행히 대부분은 결과적으로 좋은 습관이다.

가장 좋은 시작 방법은 문제가 없을 때 디버거 사용을 연습하는 것이다. 단계별로 진행하면서 할당을 살펴보고, 잘못된 값이 있을 경우 어떻게 할지 생각해 보라.

2. 로그 가능(Loggable)
런타임에 연결해서 코드를 단계별로 진행하기 위한 현대적 디버거가 있다 해도 세상은 그렇게 돌아가지 않는다. 즉, 코드가 실행되는 환경은 다양하다. 서버리스일 수도 있고 멀티스레드 환경이거나 분산 환경 또는 어딘가의 클라우드에서 실행되는 경우도 있을 것이다. 이러한 환경에서 코드는 개발자가 컴퓨터에서 실행할 때와 다르게 동작할지도 모른다.

따라서 로그가 필요하다. 그 말은 로깅 프레임워크가 필요하다는 의미다. 코드를 쓰고 로그를 읽을 수 있도록, 또는 최소한 일종의 로그 리더에서 소화가 가능하도록 로깅을 설정해야 한다. 소프트웨어에 로그 기능을 넣어야 한다.

이 부분을 못할 경우 프로덕션 배포에서 프로덕션 문제를 디버그하기 위해 로깅 코드를 배포하는 상황이 온다. 달리 말하자면 머리에 불이 붙은 채로 정유 공장으로 들어가는 격이다.

3. 테스트 가능(Testable)
이제는 단위 테스트를 써야 한다. 양보할 수 없는 경계선이다. “단위 테스트를 써야 할 비즈니스 케이스”를 논의하느라 발목을 잡는 회사에서 일한다면 떠나라. 그 논의 뒤에 따라오는 것은 혼돈의 지옥이다.

테스트 가능한 소프트웨어는 증명할 수 있는 간단한 작업을 수행하는 이해 가능한 함수로 분해된다. 테스트 가능한 소프트웨어는 더 효율적이고 탄력적이다.

테스트가 없는 레거시 코드가 있다면 그 코드를 수정하는 과정에서도 테스트를 작성하라. 가능하다면 테스트를 먼저 써라! 테스트 가능하게 만들기 위해 일부 구조를 변경해야 한다면 변경하라(조금씩이라도 해야 함).

4. 빠른 실패 가능(Fast-failable)
불안정한 코드의 가장 큰 경고 신호는 if (myvar == null) myvar ="";과 같은 코드 라인이다. 이 라인을 쓴 개발자는 간헐적 동작의 길로 개발자를 끌어내린다. 예측 가능하다는 면에서 차라리 지옥이 간헐적 행동의 영역보다 낫다. 간헐적 동작은 끊임없는 긴장감을 주다가 결국 모든 것이 눈앞에서 무너지는 그 특별한 순간(보통 비즈니스를 위해 매우 중요한 날, 또는 릴리스를 앞둔 새벽 2시)을 선사한다.

어떤 일이 “일어날 수 없다면” 일어나서는 안 된다. 일어난다면 코드는 확실한 오류 메시지를 보여주고 종료되어야 한다. 보기 좋지는 않지만 이는 문제가 해결되고 더 이상의 하향 문제를 유발하지 않음을 의미한다.

5. 멱등적(Idempotent)
특이한 이 단어는 “다시 할 때는 문제가 되지 않는다”는 의미다. 멱등적일 수 없는 요소도 있지만 가능한 모든 것은 멱등적이어야 한다. 멱등적이라는 것은, 예를 들어 거래 페이지를 새로 고친다고 해서(이전 페이지의 검증된 토큰이 있다는 이유만으로) 주문이 두 번 되는 경우가 없음을 의미한다. 대신 새로 고치면 주문 상태가 표시된다.

멱등적 동작이 코드 전반에 스며들어야 한다. 이는 디버거에서 라인 하나를 변경하고 이 변경이 문제를 수정하는지 여부를 확인할 때 디버거가 루틴의 시작으로 돌아가고 정상적으로 작동한다는 것을 의미한다. 이렇게 하면 데이터가 쉽사리 엉키지 않는 예측 가능한 소프트웨어를 만들 수 있다.

6. 불변적(Immutable)
기능적인 코드가 이를 위한 기반을 만들었다. 변수가 있으면 변수는 한 번 할당된 다음 새 데이터 구조로 결과를 변경한다. 기능적 코드를 쓰지 않더라도 불변성을 확보할 수 있다.

불변적 코드는 더 탄력적이고 온갖 종류의 쓰레드 문제를 피해간다. 불변적(또는 기능적 또는 객체 지향) 코드가 특수한 사례(예를 들어 저수준 인터프리터 작성)에 맞지 않는 저수준적 이유(디스패치의 수)가 있지만, 대다수 개발자가 쓰는 일반적인 비즈니스 코드에는 이러한 이유가 존재하지 않는다. (첫 I/O 호출보다 중요한 디스패치 최적화는 없다는 의미)

7. 이해 가능(Intelligible)
함수형 프로그래밍이 유행하자 사람들은 여기에 다소 과하게 몰입하면서 코드는 여전히 가독성이 있어야 한다는 사실을 망각했다. 10년 20년 전의 객체 지향 코드도 마찬가지다.

첫 번째 원칙 : 코드를 컴퓨터가 읽을 수 있게 만드는 것은 컴파일이다. 개발자가 할 일은 코드를 사람이 읽을 수 있게 작성하는 것이다.

8. 수정 가능(Modifiable)
얼마 전에 필자는 일부 코드에서(세션 컨텍스트에서 사용자 이름 포착) 극히 간단한 변경 작업을 하려고 했다. 애초에 이 코드가 작성된 방식 탓에 사용자 이름을 잡아서 필요한 곳으로 보내려면 많은 부분을 풀어서 수정해야 했다. 달리 말하자면 그 코드를 쓴 당사자를 제외하면 수정 가능성이 상당히 떨어지는 코드였다. 그 사람이 무슨 설계 원칙을 사용했든 문서화되지 않았고 다음에 그 코드를 물려받은 개발자가 쉽게 알아보고 이해할 수도 없다.

각 클래스, 모듈, 함수 등은 앞으로 어느 부분이든 바뀔 수 있다는 점을 염두에 두고 작성해야 한다. 데이터가 더 필요할 수도 있다(보안 정보 또는 컨텍스트). 무엇이든 항상 버전 2가 나온다는 점을 유념하고 코드를 작성하라.

객체 지향 코드에서 이는 방대한 상속 구조를 만들지 않음을 의미한다. 함수형 프로그래밍에서는 지나치게 세분화되지 않은 또는 컨텍스트가 있는 매개변수를 사용하는 함수를 설계하는 것을 의미한다.

9. 문서화 가능(Documentable)
좋은 코드는 그 자체가 문서라고 할 수 있다. 관건은 클래스, 함수, 변수의 명명이다. 또한 JavaDoc(또는 개발자가 사용하는 언어의 상응 개념)과 코드 설계 역시 중요하다.

 “이것”과 “이것이 하는 일”을 문서화하기 위해 10가지 다른 부분을 참조해야만 한다면 그 설계는 다시 생각해야 한다.

10. 모듈형(Modular)
좋은 코드는 적어도 필요한 데이터를 공급하는 도구를 사용해서 독립적으로 실행 및 수정할 수 있는 논리적 부분으로 분할된다.

11. 빌드 가능(Buildable)
다른 개발자가 깃(Git)에서 코드를 받아 간단히 빌드를 실행할 수 없다면 그 빌드는 개선이 필요한 것이다. 물론 메이븐(Maven)이나 메이크(Make) 같은 “유명한” 빌드 툴이 필요한 정도까지는 괜찮다. 그러나 일주일, 몇 날 며칠 또는 몇 시간의 프로세스가 필요하다면 그 빌드는 고쳐야 한다.

새 팀원이 올 때, 개발자가 새 컴퓨터를 받을 때, 또는 중요한 핫픽스 릴리스 중에 매번 빌드와 씨름해야 한다면 이는 팀에 크나큰 부담이 될 것이다.  editor@itworld.co.kr


2018.04.16

좋은 코드를 작성하고 있다는 징후 11가지

Andrew C. Oliver | InfoWorld
어떤 개발언어를 사용하든 어떤 기술을 사용하든 자신이 만드는 코드를 이런 형용사로 설명할 수 있다면, 좋은 코드는 따라오기 마련이다.

빌 소러는 윤리적 관점에서 스스로 부끄러움을 느낀 코드에 대한 좋은 글을 미디엄(Medium)에 올린 적이 있다. 그러나 기술적인 측면에서도 소프트웨어를 부끄러워해야 이유는 많다. 부끄러워하지 않을 소프트웨어를 나타내는 11가지 단어를 살펴보자.



사용하는 언어 또는 기술 스택이 무엇이든 코드를 다음 단어로 설명할 수 있다면 좋은 코드일 가능성이 높다. 자신의 코드에 몇 개나 적용되는지 확인해 보라.

1. 디버그 가능(Debuggable)
대부분의 현대 런타임에서는 일종의 디버거를 연결할 수 있다. Node.js조차 비주얼 스튜디오로 디버그가 가능하다. 언젠가 무슨 일이 벌어지는지 알아내야 할 때 디버거를 연결해야 한다는 생각을 갖고 코드를 써야 한다.

그 의미를 정확히 설명하기는 어렵지만 이렇게 하면 때로는 데이터의 구조를 설계하는 데 영향을 미치기도 한다. 구조를 파나갈 필요가 없도록 더 많은 임시 변수를 사용할 수도 있다. 다행히 대부분은 결과적으로 좋은 습관이다.

가장 좋은 시작 방법은 문제가 없을 때 디버거 사용을 연습하는 것이다. 단계별로 진행하면서 할당을 살펴보고, 잘못된 값이 있을 경우 어떻게 할지 생각해 보라.

2. 로그 가능(Loggable)
런타임에 연결해서 코드를 단계별로 진행하기 위한 현대적 디버거가 있다 해도 세상은 그렇게 돌아가지 않는다. 즉, 코드가 실행되는 환경은 다양하다. 서버리스일 수도 있고 멀티스레드 환경이거나 분산 환경 또는 어딘가의 클라우드에서 실행되는 경우도 있을 것이다. 이러한 환경에서 코드는 개발자가 컴퓨터에서 실행할 때와 다르게 동작할지도 모른다.

따라서 로그가 필요하다. 그 말은 로깅 프레임워크가 필요하다는 의미다. 코드를 쓰고 로그를 읽을 수 있도록, 또는 최소한 일종의 로그 리더에서 소화가 가능하도록 로깅을 설정해야 한다. 소프트웨어에 로그 기능을 넣어야 한다.

이 부분을 못할 경우 프로덕션 배포에서 프로덕션 문제를 디버그하기 위해 로깅 코드를 배포하는 상황이 온다. 달리 말하자면 머리에 불이 붙은 채로 정유 공장으로 들어가는 격이다.

3. 테스트 가능(Testable)
이제는 단위 테스트를 써야 한다. 양보할 수 없는 경계선이다. “단위 테스트를 써야 할 비즈니스 케이스”를 논의하느라 발목을 잡는 회사에서 일한다면 떠나라. 그 논의 뒤에 따라오는 것은 혼돈의 지옥이다.

테스트 가능한 소프트웨어는 증명할 수 있는 간단한 작업을 수행하는 이해 가능한 함수로 분해된다. 테스트 가능한 소프트웨어는 더 효율적이고 탄력적이다.

테스트가 없는 레거시 코드가 있다면 그 코드를 수정하는 과정에서도 테스트를 작성하라. 가능하다면 테스트를 먼저 써라! 테스트 가능하게 만들기 위해 일부 구조를 변경해야 한다면 변경하라(조금씩이라도 해야 함).

4. 빠른 실패 가능(Fast-failable)
불안정한 코드의 가장 큰 경고 신호는 if (myvar == null) myvar ="";과 같은 코드 라인이다. 이 라인을 쓴 개발자는 간헐적 동작의 길로 개발자를 끌어내린다. 예측 가능하다는 면에서 차라리 지옥이 간헐적 행동의 영역보다 낫다. 간헐적 동작은 끊임없는 긴장감을 주다가 결국 모든 것이 눈앞에서 무너지는 그 특별한 순간(보통 비즈니스를 위해 매우 중요한 날, 또는 릴리스를 앞둔 새벽 2시)을 선사한다.

어떤 일이 “일어날 수 없다면” 일어나서는 안 된다. 일어난다면 코드는 확실한 오류 메시지를 보여주고 종료되어야 한다. 보기 좋지는 않지만 이는 문제가 해결되고 더 이상의 하향 문제를 유발하지 않음을 의미한다.

5. 멱등적(Idempotent)
특이한 이 단어는 “다시 할 때는 문제가 되지 않는다”는 의미다. 멱등적일 수 없는 요소도 있지만 가능한 모든 것은 멱등적이어야 한다. 멱등적이라는 것은, 예를 들어 거래 페이지를 새로 고친다고 해서(이전 페이지의 검증된 토큰이 있다는 이유만으로) 주문이 두 번 되는 경우가 없음을 의미한다. 대신 새로 고치면 주문 상태가 표시된다.

멱등적 동작이 코드 전반에 스며들어야 한다. 이는 디버거에서 라인 하나를 변경하고 이 변경이 문제를 수정하는지 여부를 확인할 때 디버거가 루틴의 시작으로 돌아가고 정상적으로 작동한다는 것을 의미한다. 이렇게 하면 데이터가 쉽사리 엉키지 않는 예측 가능한 소프트웨어를 만들 수 있다.

6. 불변적(Immutable)
기능적인 코드가 이를 위한 기반을 만들었다. 변수가 있으면 변수는 한 번 할당된 다음 새 데이터 구조로 결과를 변경한다. 기능적 코드를 쓰지 않더라도 불변성을 확보할 수 있다.

불변적 코드는 더 탄력적이고 온갖 종류의 쓰레드 문제를 피해간다. 불변적(또는 기능적 또는 객체 지향) 코드가 특수한 사례(예를 들어 저수준 인터프리터 작성)에 맞지 않는 저수준적 이유(디스패치의 수)가 있지만, 대다수 개발자가 쓰는 일반적인 비즈니스 코드에는 이러한 이유가 존재하지 않는다. (첫 I/O 호출보다 중요한 디스패치 최적화는 없다는 의미)

7. 이해 가능(Intelligible)
함수형 프로그래밍이 유행하자 사람들은 여기에 다소 과하게 몰입하면서 코드는 여전히 가독성이 있어야 한다는 사실을 망각했다. 10년 20년 전의 객체 지향 코드도 마찬가지다.

첫 번째 원칙 : 코드를 컴퓨터가 읽을 수 있게 만드는 것은 컴파일이다. 개발자가 할 일은 코드를 사람이 읽을 수 있게 작성하는 것이다.

8. 수정 가능(Modifiable)
얼마 전에 필자는 일부 코드에서(세션 컨텍스트에서 사용자 이름 포착) 극히 간단한 변경 작업을 하려고 했다. 애초에 이 코드가 작성된 방식 탓에 사용자 이름을 잡아서 필요한 곳으로 보내려면 많은 부분을 풀어서 수정해야 했다. 달리 말하자면 그 코드를 쓴 당사자를 제외하면 수정 가능성이 상당히 떨어지는 코드였다. 그 사람이 무슨 설계 원칙을 사용했든 문서화되지 않았고 다음에 그 코드를 물려받은 개발자가 쉽게 알아보고 이해할 수도 없다.

각 클래스, 모듈, 함수 등은 앞으로 어느 부분이든 바뀔 수 있다는 점을 염두에 두고 작성해야 한다. 데이터가 더 필요할 수도 있다(보안 정보 또는 컨텍스트). 무엇이든 항상 버전 2가 나온다는 점을 유념하고 코드를 작성하라.

객체 지향 코드에서 이는 방대한 상속 구조를 만들지 않음을 의미한다. 함수형 프로그래밍에서는 지나치게 세분화되지 않은 또는 컨텍스트가 있는 매개변수를 사용하는 함수를 설계하는 것을 의미한다.

9. 문서화 가능(Documentable)
좋은 코드는 그 자체가 문서라고 할 수 있다. 관건은 클래스, 함수, 변수의 명명이다. 또한 JavaDoc(또는 개발자가 사용하는 언어의 상응 개념)과 코드 설계 역시 중요하다.

 “이것”과 “이것이 하는 일”을 문서화하기 위해 10가지 다른 부분을 참조해야만 한다면 그 설계는 다시 생각해야 한다.

10. 모듈형(Modular)
좋은 코드는 적어도 필요한 데이터를 공급하는 도구를 사용해서 독립적으로 실행 및 수정할 수 있는 논리적 부분으로 분할된다.

11. 빌드 가능(Buildable)
다른 개발자가 깃(Git)에서 코드를 받아 간단히 빌드를 실행할 수 없다면 그 빌드는 개선이 필요한 것이다. 물론 메이븐(Maven)이나 메이크(Make) 같은 “유명한” 빌드 툴이 필요한 정도까지는 괜찮다. 그러나 일주일, 몇 날 며칠 또는 몇 시간의 프로세스가 필요하다면 그 빌드는 고쳐야 한다.

새 팀원이 올 때, 개발자가 새 컴퓨터를 받을 때, 또는 중요한 핫픽스 릴리스 중에 매번 빌드와 씨름해야 한다면 이는 팀에 크나큰 부담이 될 것이다.  editor@itworld.co.kr


X