코딩한걸음
article thumbnail
728x90
반응형

책 소개

 

이 책, "내 코드가 그렇게 이상한가요?"는 개발자들이 자주 저지르는 코드 설계 및 구현의 오류를 지적하고, 이를 개선하기 위한 방안을 제시합니다. 코드의 가독성, 유지보수성, 확장성을 고려한 클린 코드 작성법에 대해 다룹니다.

 

구매 링크 : 내 책이 그렇게 이상한가요?

 

 

 

설계의 문제점 파악하기

의도를 분명히 전달할 수 있는 이름 정하기

  • 문제: 변수, 함수, 클래스 등의 이름이 그 역할이나 의도를 명확하게 전달하지 못할 때, 코드의 가독성과 이해도가 크게 떨어집니다.
  • 해결: 이름을 정할 때는 해당 코드 조각의 목적, 역할, 사용 방법을 명확하게 반영하는 이름을 사용해야 합니다. 이는 후에 코드를 읽는 다른 개발자가 코드의 의도를 쉽게 파악할 수 있게 합니다.

 

목적별로 변수를 따로 할당하기

  • 문제: 하나의 변수가 여러 목적으로 사용될 때, 코드의 복잡성이 증가하고 오류가 발생하기 쉬워집니다.
  • 해결: 각 변수는 단일 목적으로 사용되어야 합니다. 변수의 재사용을 최소화함으로써, 코드의 명확성과 안정성을 높일 수 있습니다.

 

단순 나열이 아닌 의미있는 것을 모아 메서드 만들기

  • 문제: 코드가 단순히 나열되어 있고, 각 부분이 논리적인 단위로 묶이지 않을 때, 코드를 이해하고 관리하기가 어렵습니다.
  • 해결: 관련된 로직을 의미 있는 단위로 묶어 메서드로 만듭니다. 이는 코드의 재사용성을 높이고, 가독성을 개선하며, 유지보수를 용이하게 합니다.

 

관련된 데이터와 로직을 클래스로 모으기

  • 문제: 데이터와 이를 처리하는 로직이 분산되어 있을 때, 코드의 응집력이 떨어지고, 관리가 복잡해집니다.
  • 해결: 관련된 데이터와 로직을 하나의 클래스로 묶어 캡슐화합니다. 이는 데이터와 로직의 응집력을 높이고, 객체 지향의 원칙에 부합하는 설계를 가능하게 합니다.

 

 

 

클래스 설계

클래스의 구성 요소와 자기 방어 임무

  • 클래스의 구성 요소: 클래스는 속성(attributes)과 메서드(methods)로 구성됩니다. 속성은 클래스의 상태를 나타내는 변수이며, 메서드는 클래스의 행동을 정의합니다.
  • 자기 방어 임무: 클래스는 잘못된 사용으로부터 자신을 보호해야 합니다. 이는 유효하지 않은 값이 속성에 할당되는 것을 방지하고, 클래스의 상태가 예상대로 유지되도록 하는 메커니즘을 포함해야 합니다.

 

성숙한 클래스로 성장시키는 설계 기법

  1. 생성자로 확실하게 정상적인 값 설정하기: 생성자(__init__) 메서드를 사용하여 인스턴스가 생성될 때 유효한 초기 상태를 갖도록 합니다. 이를 통해 객체의 불완전한 상태를 방지할 수 있습니다.
  2. 계산 로직도 데이터를 가진 쪽에 구현하기: 관련 데이터를 소유한 클래스 내부에 계산 로직을 구현함으로써 캡슐화를 강화하고, 코드의 가독성과 재사용성을 높입니다.
  3. 불변 변수로 만들어서 예상하지 못한 동작 막기: 속성을 불변(immutable)으로 만들어 객체가 생성된 후에는 상태가 변경되지 않도록 함으로써, 코드를 더 예측 가능하게 만듭니다.
  4. 변경하고 싶다면 새로운 인스턴스 만들기: 객체의 상태를 변경해야 할 필요가 있을 때는 객체의 상태를 직접 변경하는 대신, 변경된 상태를 가진 새로운 인스턴스를 생성하는 방법을 고려합니다. 이는 함수형 프로그래밍의 원칙을 따르는 방법입니다.
  5. 메서드 매개변수와 지역변수도 불변으로 만들기: 가능하면 메서드의 매개변수와 지역변수도 불변으로 선언하여, 메서드 실행 도중 예상치 못한 변경을 방지합니다.
  6. 엉뚱한 값을 전달하지 않도록 만들기: 메서드에 전달되는 인자에 대해 유효성 검사를 수행하여, 잘못된 값이 전달되는 것을 방지합니다.
  7. 의미 없는 메서드를 추가하지 않기: 클래스의 기능과 직접적인 관련이 없거나, 잘못 사용될 가능성이 있는 메서드는 추가하지 않습니다.

 

조건 분기와 가독성

  • 조기 리턴으로 중첩 제거하기: 조건 분기가 깊게 중첩되어 코드의 가독성이 떨어지는 경우, 조기 리턴(early return)을 사용하여 중첩을 줄입니다. 이는 함수의 나머지 부분이 주 실행 경로를 나타내도록 하여 코드를 더 읽기 쉽게 만듭니다.
  • 가독성을 낮추는 else 구문도 조기 리턴으로 해결하기: if문 다음에 오는 else 구문이 필요없게 만들어 조기 리턴을 사용하면, 코드의 가독성을 높이고 불필요한 중첩을 제거할 수 있습니다.

 

 

 

설계의 건전성을 해치는 여러 요소들

데드 코드 (Dead Code)

  • 정의: 절대로 실행되지 않는 코드를 말합니다. 이러한 코드는 프로그램의 실행에 영향을 주지 않으며, 리소스 낭비와 혼란만 초래합니다.
  • 해결 방법: 정기적으로 코드 리뷰와 리팩토링을 실시하여 데드 코드를 식별하고 제거합니다. 최신 IDE나 정적 분석 도구를 사용하면 도달 불가능한 코드를 쉽게 찾아낼 수 있습니다.

 

YAGNI 원칙

  • 정의: "You Aren’t Gonna Need It"의 약자로, 현재 필요하지 않은 기능은 구현하지 말라는 원칙입니다.
  • 해결 방법: 실제 필요한 기능에 초점을 맞추고, 이를 기반으로 코드를 작성합니다. 미래의 요구 사항은 변경될 수 있으므로, 그때가 되어서 필요한 기능을 추가하는 것이 좋습니다.

 

매직 넘버

  • 정의: 코드 내에서 직접적으로 사용되어 그 의미를 파악하기 어려운 숫자입니다.
  • 해결 방법: 매직 넘버 대신 의미 있는 이름을 가진 상수를 사용합니다. 이렇게 하면 코드의 가독성이 크게 향상됩니다.

 

문자열 자료형에 대한 집착

  • 문제: 하나의 문자열 변수에 여러 값을 저장하여 사용하는 경우, 나중에 이를 분리하거나 재사용하기 어렵습니다.
  • 해결 방법: 가능하면 구조화된 데이터 타입(예: 클래스, 튜플, 리스트)을 사용하여 각 값에 명확한 의미를 부여하고, 접근 및 관리를 용이하게 합니다.

 

전역 변수

  • 문제: 전역 변수는 어디서든 접근할 수 있어, 프로그램의 상태를 예측하기 어렵게 만들고 디버깅을 어렵게 합니다.
  • 해결 방법: 가능한 한 지역 변수를 사용하고, 필요한 경우 클래스나 모듈을 통해 캡슐화하여 데이터를 관리합니다.

 

null 문제

  • 문제: null은 초기화되지 않은 상태를 나타내며, 이로 인해 예기치 않은 오류가 발생할 수 있습니다.
  • 해결 방법: 가능한 한 null 대신 '빈 객체 패턴', '옵셔널(Optional)', 또는 기본값을 사용하여, 프로그램이 더 안정적이고 예측 가능하게 만듭니다. 예를 들어, 캐릭터가 무기를 장착하지 않은 상태를 null 대신 '빈 무기' 객체로 표현할 수 있습니다.

 

 

 

이름 설계 : 구조를 파악할 수 있는 이름

이름 설계 방법

  1. 관심사 분리 (Separation of Concerns): 코드를 유스케이스, 목적, 역할 등으로 구분하고, 각각에 적합한 이름을 붙입니다. 이는 코드의 복잡성을 줄이고, 각 부분의 목적을 명확하게 합니다.
  2. 목적 중심 이름 설계: 이름을 지을 때, 그것의 존재보다는 목적을 기반으로 생각합니다. 이는 코드의 가독성을 높이고, 의도를 더 잘 전달할 수 있게 합니다.
    • 구체적이고, 의미 범위가 좁은 이름 선택: 비즈니스 목적에 맞게 이름을 명명하면, 관련 없는 로직을 배제하기 쉬워지고, 클래스의 크기가 작아지며, 결합도가 낮아지는 등 여러 가지 이점이 있습니다.
  3. 소리 내어 이야기해 보기: 이름을 결정할 때, 그 이름을 소리 내어 말해보는 것이 도움이 될 수 있습니다. 이는 이름이 자연스럽고 이해하기 쉬운지를 판단하는 데 유용합니다.
  4. 이용 약관 읽어보기: 이름이 그 기능이나 역할을 적절히 설명하는지, 사용하는 컨텍스트에서 자연스러운지 검토합니다.
  5. 다른 이름으로 대체할 수 없는지 검토하기: 가능한 한 명확하고, 대체할 수 없는 이름을 사용해야 합니다. 이는 해당 이름이 그 기능이나 역할에 정확히 부합함을 의미합니다.

 

이름 설계 시 주의 사항

  • 의미 범위 변경 경계하기: 사양이 변경될 때 이름의 의미 범위가 변경되지 않도록 주의합니다. 이는 잘못된 추상화로 이어질 수 있습니다.
  • 코드에 등장하지 않은 이름 주의하기: 대화에서는 등장하지만 코드에 등장하지 않는 이름이 있다면, 이는 중요한 개념이 코드에 반영되지 않았을 가능성이 있습니다.
  • 수식어를 붙여서 구별해야 하는 경우 클래스로 만들어 보기: 비슷한 역할을 하지만 약간의 차이가 있는 경우, 수식어를 붙여 구분하기보다는 별도의 클래스로 분리하는 것이 좋습니다.

 

구조에 악영향을 미치는 이름

  • 데이터 클래스처럼 보이는 이름, 거대한 이름 (예: manager, processor, controller): 이러한 이름은 클래스가 너무 많은 책임을 지고 있을 가능성을 시사합니다. 이름이 클래스의 실제 역할을 반영하지 못하게 되면, 코드의 구조가 불분명해질 수 있습니다.
  • 상황에 따라 의미가 달라질 수 있는 이름: 이는 코드의 이해를 어렵게 하고, 오해의 소지를 높일 수 있습니다.
  • 일련번호 명명: 가능하면 의미 있는 이름을 사용하고, 일련번호를 통한 명명은 피해야 합니다.

 

이름 축약 피하기

  • 의도를 알 수 없는 축약: 코드의 가독성을 해치고, 이해하는 데 더 많은 시간이 소요될 수 있습니다. 기본적으로 이름은 축약하지 않는 것이 좋습니다.

 

 

 

주석 : 유지 보수와 변경의 정확성을 높이는 주석 작성 방법

내용이 낡은 주석

  • 구현 변경 시 주석도 함께 변경: 코드의 로직이 변경될 때, 관련된 주석도 반드시 갱신해야 합니다. 이는 후에 코드를 읽는 사람이 혼란을 겪지 않도록 보장합니다.
  • 실제 코드가 아님을 이해하기: 주석은 실행되지 않으므로, 코드의 동작과 직접적인 연결이 없습니다. 이러한 이유로, 주석만을 신뢰하고 코드를 제대로 확인하지 않으면 문제가 발생할 수 있습니다.
  • 로직 설명보다는 의도 설명에 집중: 코드의 로직 자체보다는, 로직이 왜 그렇게 작성되었는지, 어떤 의도로 작성되었는지 설명하는 주석이 더 유용합니다.

 

주석 때문에 이름을 대충 짓는 예

  • 메서드의 가독성을 높이기: 메서드 이름을 명확하게 지어서 메서드의 기능을 잘 설명할 수 있다면, 추가적인 주석 없이도 코드의 의도를 명확히 할 수 있습니다. 메서드 이름이나 변수 이름으로 코드의 의도를 전달하는 것이 주석을 달아 설명하는 것보다 더 바람직할 수 있습니다.

 

주석 규칙 정리

  • 로직 변경 시 주석도 변경: 코드를 수정할 때마다 관련 주석도 꼭 확인하고 필요하면 수정해야 합니다. 이는 주석과 코드 사이의 불일치를 방지합니다.
  • 로직의 내용을 단순 설명하는 주석은 피하기: 코드가 자체적으로 명확하게 의도를 전달할 수 있도록 작성해야 합니다. 코드의 가독성을 높이는 것이 우선이며, 이를 통해 불필요한 주석을 줄일 수 있습니다.
  • 가독성이 나쁜 로직에 주석을 달지 말고 로직을 개선하기: 복잡한 로직에 대한 설명보다는 코드 자체를 더 이해하기 쉽게 만드는 것이 중요합니다. 코드 리팩토링을 통해 가독성을 높이세요.
  • 로직의 의도와 사양 변경에 대한 주의사항을 주석으로 추가: 코드의 특정 부분이 왜 존재하는지, 특정 결정이 왜 내려졌는지 설명하는 주석은 유지보수에 큰 도움이 됩니다. 또한, 후속 개발자가 사양 변경이나 코드 수정을 할 때 참고할 수 있는 중요한 정보를 제공합니다.

 

문서 주석

  • API 문서화에 사용: 함수, 클래스, 모듈 등의 인터페이스를 설명하기 위해 사용되는 문서 주석은, 해당 코드를 사용하는 개발자에게 필요한 정보를 제공합니다.
  • 도구를 활용한 문서 자동화: Python에서는 docstring을 사용하여 문서 주석을 작성하고, Sphinx 같은 도구를 이용해 이를 HTML 문서 등으로 자동 변환할 수 있습니다.

 

 

 

메서드 : 좋은 클래스에는 좋은 메서드가 있다

반드시 현재 클래스의 인스턴스 변수 사용하기

메서드는 해당 클래스의 인스턴스 변수를 사용하여 클래스의 상태를 읽거나 변경해야 합니다. 이는 캡슐화 원칙을 강화하고, 클래스의 책임을 명확하게 합니다.

 

불변을 활용해서 예상할 수 있는 메서드 만들기

불변 객체를 사용하면 메서드의 동작을 예측하기 쉬워집니다. 불변 객체는 상태가 변경되지 않으므로, 메서드 호출 전후의 객체 상태를 쉽게 이해할 수 있습니다.

 

묻지 말고 명령하라

객체의 상태를 외부에서 확인한 후 조작하는 대신, 해당 객체에 명령을 내려 필요한 로직을 수행하도록 해야 합니다. 이는 객체의 자율성을 높이고, 복잡성을 줄입니다.

 

커맨드 / 쿼리 분리

  • 커맨드(Command): 객체의 상태를 변경하는 메서드입니다.
  • 쿼리(Query): 객체의 상태 정보를 반환하는 메서드로, 객체의 상태를 변경하지 않습니다.
  • 모디파이어(Modifier): 커맨드와 쿼리를 동시에 수행하는 것은 피해야 합니다. 이는 메서드의 행동을 예측하기 어렵게 만들 수 있습니다.

 

매개변수

  • 불변 매개변수 사용: 메서드에 전달되는 매개변수는 가능한 한 불변으로 만들어야 합니다. 이는 메서드 내에서 부작용(side effect)을 방지합니다.
  • 플래그 매개변수 사용하지 않기: 메서드의 동작 모드를 변경하는 플래그 매개변수는 사용하지 않는 것이 좋습니다. 대신, 메서드를 분리하는 것을 고려해야 합니다.
  • null 전달하지 않기: 메서드에 null을 전달하는 대신, 옵셔널(Optional) 타입이나 특수 사례 객체를 사용합니다.
  • 출력 매개변수 사용하지 않기: 메서드의 결과를 반환할 때는 리턴 값을 사용하고, 출력 매개변수는 사용하지 않습니다.
  • 매개변수는 최대한 적게 사용하기: 메서드의 매개변수 수를 최소화하면, 메서드의 사용이 간단해지고 이해하기 쉬워집니다.

 

리턴 값

  • ‘자료형’을 사용해서 리턴 값의 의도 나타내기: 리턴 값의 타입을 통해 메서드의 의도를 명확하게 전달할 수 있습니다.
  • null 리턴하지 않기: 메서드에서 null을 리턴하는 대신, 옵셔널(Optional)을 리턴하거나 빈 컬렉션을 리턴하는 등의 방법을 사용합니다.
  • 오류는 리턴값으로 리턴하지 말고 예외 발생시키기: 실패 상황을 처리할 때는 예외를 사용하여 오류 상황을 명확하게 표현합니다. 이는 오류 처리 로직을 정상 로직에서 분리할 수 있게 해줍니다.

 

소감

신입 개발자로서, 항상 “내 코드 작성 방식이 적절할까?” 라는 생각이 들었다. 이 책은 위와같은 의문이 있는 개발자들에게 방향을 제시해준다. 효율성만을 추구하는 것이 아닌, 가독성, 유지보수성, 확장성 그리고 협업의 용이성을 고려한 코드 설계의 중요성을 강조한 점이 특히 좋았다.

 

 

 

728x90
반응형
profile

코딩한걸음

@Joonyeol_Yoon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!