인프런 워밍업 스터디 클럽 2기 백엔드(클린코드, 테스트코드) 2주차 발자국

인프런 워밍업 스터디 클럽 2기 백엔드(클린코드, 테스트코드) 2주차 발자국

블로그 글은 박우빈님의 인프런 강의 참조하여 작성한 글입니다.

어느덧 인프런 워밍업 스터디 클럽을 시작한지도 2주째가 시작된다. 그리고 이번주 1주에 대한 회고를 시작해보려고 한다.

이번주도 여러가지를 배우고 많은 경험이 된 한 주였다. 그럼 회고를 시작하겠다. 완주 및 우수러너를 위해 오늘도 달려본다.

주석의 양면성

클린코드 관점에서 주석은 죄악이냐 아니냐 논쟁이 많다.

  • 주석이 많다는 것은 그만큼 비즈니스 요구사항을 코드에 잘못 녹였다는 이야기

  • 코드를 설명하는 주석을 쓰면 코드가 아니라 주석에 의존한다. 주석에 의존하여 코드를 작성하면 적절하지 않은 추상화 레벨을 갖게 되어 낮은 품질의 코드가 만들어 진다.

     

🙋🏻 아니 그러면 주석 언제 써요?

- 우리가 리팩토링 할때 정말 난관 하나가 히스토리를 전혀 없는 코드다.

- 후대에 전해야 "의사결정의 히스토리" 도저히 코드로 표현할 없을 주석으로 상세히 설명한다.

- 주석을 작성할 자주 변하는 정보는 최대한 지양해서 작성한다.

- 만약 관련 정책이 변하거나 코드가 변경되었다면 주석도 잊지 않고 함께 업데이트 해야한다. 주석이 없는 코드보다 부정확한 주석이 달린 코드가 치명적이다.

우리가 가진 모든 표현방법을 총동원해 코드에 의도를 녹여내고 그럼에도 불구하고 전달해야할 정보가 남았을때 사용하는게 주석이다.

이번 예제 실습은 주석이 달린 gameStatus를 enum으로 변경하고 관련 비즈니스 로직을 MineSweeper가 아니라 GameBoard에 있는게 어울려 그곳으로 변경하였다.

변수와 메서드의 나열 순서

  • 변수는 사용하는 순서대로 나열한다.

    • 인지적 경제성

  • 메서드의 순서도 고려해보아야 하는데 객체의 입장에서 생각하자.

  • 객체는 협력을 위한 존재이다. 외부세계에 내가 어떤 기능을 제공할 수 있는지를 드러낸다. (정해진 답은 아니지만 우빈님 추천) 공개 메서드들을 상단에 배치하고 나머지를 private 메서드들로 나열한다.

  • 공개 메서드들끼리도 기준을 가지고 배치하는 것이 좋다.

    • 객체지향을 하다보면 중요한 객체의 경우 메서드가 수십개까지도 늘어날 수 있는데 중요도 순, 종류별로 그룹화하여 배치하면 실수로 비슷한 조직의 메서드를 중복으로 만드는 것을 일관성 있는 로직을 유지할 수 있다.

    • 상태변경을 최우선, 그 이후는 판별, 조회로직이 있는 메서드들 순으로 한다.

  • 비공개 메서드는 공개 메서드에서 언급한 순서대로 배치한다.

  • 공통으로 사용하는 메서드라면 (가장 하단과 같이) 적당한 곳에 배치한다.

     

이를 통해 우리의 예시 코드도 이와 같이 리팩토링 하는 작업을 해보았다.

패키지 나누기

  • 패키지는 문맥으로써의 정보를 제공할 수 있다.

  • 패키지를 쪼개지 않으면 관리가 어려줘 진다.

  • 패키지를 너무 잘게 쪼개도 마찬가지로 관리가 어려워진다.

  • 대규모 패키지 변경은 팀원과의 합의를 이룬 시점에 하자.

    • 현재 기준으로 본인만 변경하고 있는 부분이라면 괜찮으나 여러 사람이 변경중인 부분이나 공통으로 사용하는 클래스들의 패키지를 한번에 변경하면 추후 브랜츠 충돌이 날 수 있다.

    • 따라서 처음 만들때부터 잘 고민해서 패키지를 나누는 것이 좋다.

       

기능 유지보수하기 (1) - 버그 잡기

해당 시간에는 깃발이 전부 꼽았을때 승리조건으로 가는 오류를 고쳐보았다. 이 수정을 통해 우리가 객체지향적으로 작성하여 수정될 곳이 적었지만 만약 이전의 코드였다면 여러군데 고쳐야 할 우려가 있었을 것이다.

기능 유지보수하기 (2) - 알고리즘 교체하기

  • DFS(깊이 우선 탐색) -> 재귀, Stack

  • BFS(너비 우선 탐색) -> Queue

  • 재귀를 이용한 DFS도 결국 stack이다.

    • 스레드마다 생기는 스택영역에는 함수를 호출할 때마다 frame이 쌓인다.

    • frame은 지역변수, 연산을 위한 정보등을 담고 있다.

    • stack영역은 결국 크기가 제한되어 있다.

  • 우리가 필요한건 각 Cell의 모든 메서드 정보가 아니다. 각 Cell의 CellPosition만 있다면 원하는 작업을 할 수 있다.

     

그래서 해당 부분을 통해 우리는 재귀 로직을 stack형태로 변경을 해보았다.

IDE의 도움 받기

읽기 좋은 코드란 결국 가독성이 좋아야 한다. 이것을 위해 IDE의 큰 도움을 받을 수 있다.

  • 코드 포맷 정렬: option + cmd + L | Ctrl + Alt + L

  • 코드 품질: SonarLint

    • lint: 잠재적인 문제가 될 수 있는 오류, 버그, 스타일등을 미리 알려주는 코드품질 체크도구

  • 포맷규칙: .editorconfig

    • 여러사람과의 협업을 염두하면 IDE의 기본 포맷팅에 익숙해지는 것이 좋다.

    • 스타일은 혼자 결정하는 것이 아니라 팀 내 합의로 도출되어야 한다.

    • 한번 정해지면 절재적인것이 아니라 사용하면서 계속 의견을 듣고 개선/반영하는 것이 좋다.

       

이런 기능들을 이용하여 우리 예제 프로젝트들도 포맷팅 및 리팩토링을 해보았다. ex) Stack -> Deque

연습프로젝트 소개

이번에는 새로운 도메인인 '스터디카페 이용 시스템'을 리팩토링하기 전 코드 해석을 하였다. 우빈님께서는 아래와 같은 사항을 중점으로 리팩토링을 해보시라고 하셨다.

1. 추상화 레벨 (메서드 추출등)

2. 객체로 묶어볼 만한 것은 없는지..

3. 객체지향 페러다임에 맞게 객체들이 상호협력하고 있는지

4. SRP: 책임에 따라 응집도 있게 객체가 잘 나뉘어져 있는지

5. DIP: 의존관계 역전을 적용할만한 곳은 없는지

6. 일급 컬렉션

그래서 미션을 진행해보고 한번 강의를 학습해야겠다.

미션3

해당 미션을 하면서 조금은 많은 부분을 느꼈다. 일단 이렇게 클린코드 관점으로 코드를 리팩토링하는 것이 처음이기에 매우 익숙치 않았고 상당히 오래 걸렸다. 일단 나 나름대로 처음에 소개해준데로 리팩토링을 해보았자만 현재 코드에 대해 나름대로 만족을 한다.

미션3 깃허브 링크

리팩토링 (1) - 추상화 레벨

해당 부분에는 예제 프로젝트에서 중복제거 및 메서드 추출 및 객체에 메세지를 보내어 getter방지를 해보았다. 내가 했던 미션과 비교를 해보니 이 부분은 대강 얼추 방향성을 잘 따란것 같다. 나는 여기서 추가적으로 라커 정책을 구현하였는데 이 부분은 내가 잘한 부분인지는 아직도 헷갈린다.

리팩토링 (2) - 객체의 책임과 응집도

이번 강의에서는 배울 점이 많았다. 나는 해당 설정 관련 부분들을 config에 빼고 해당 config를 getter로 삼는 provider로 넘겨주는 식으로 하였다. 하지만 I/O통합 부분은 진짜 강좌를 보면서 "아! 이것도 있었지.."라는 생각이 들며 조금은 반성이 되었다. 나머지 일급 컬렉션, display()의 책임 분리, Order객체로 분리하여 비즈니스 로직 이관까지는 그래도 비슷하게 갔던것 같다. 나는 거기서 조금 if문 3개로 나눠진 display를 switch문으로 변경까지 조금 읽기 쉬운 코드로 변경해보았다.

리팩토링 (3) - 관점의 차이로 달라지는 추상화

해당 부분은 나는 DIP 생각 없이 지뢰때 했던것 처럼 초기화 로직과 실행로직을 분리하고 이렇게 생각하니 FileHandler부분도 두개의 메서드를 분리할 수 있지 않을까 싶었고 그렇게 분리를 하였는데 강의에서는 DIP원칙을 적용하여 했었던것이다. 왜 그런지 모르고 그냥 기계처럼 한 것이 조금 반성스럽고 고쳐야할 부분이라 생각이 든다.

능동적 읽기

  • 복잡하거나 엉망인 코드를 읽고 이해하려 할 때 리팩토링 하면서 읽기

    • 공백으로 단락 구분하기

    • 메서드와 객체로 추상화 해보기

    • 주석으로 이해한 내용 표기하며 읽기

  • 우리에게는 언제든 돌아갈 수 있는 git reset --hard가 있다.

  • 핵심목표는 우리의 도메인 지식을 늘리는 것 그리고 이전 작성자의 의도를 파악하는 것

     

이전까지 나는 코드를 눈으로 해석하고 리팩토링 하려는 습관들이 있었다. 하지만 이번 강의를 통해 코드를 분리해보고 주석도 달아보면서 리팩토링하면서 읽어가야겠다는 습관으로 고쳐야겠다는 생각이 들었다.

오버 엔지니어링

  • 필요한 적정수준보다 더 높은 수준의 엔지니어링

    • ex) 구현체가 하나인 인터페이스

      • 인터페이스 형태가 아키텍쳐 이해에 도움을 주거나 근시일내에 구현체가 추가될 가능성이 높다면 OK.

      • 구현체를 수정할때마다 인터페이스도 수정해야함.

      • 코드 탐색에 영향을 줌 / 어플리케이션이 비대해짐

    • ex) 너무 이른 추상화

      • 정보가 숨겨지기 때문에 복잡도가 높아진다.

      • 후대 개발자들이 선대의 의도를 파악하기가 어렵다.

         

지금 이 내용을 학습해보니 이전에 미션3에서 내가 구현한 코드들이 오버엔지니어링이였지 않을까라는 생각을 하면서 반성하게 되었다.

은탄환은 없다

  • 클린코드도 은탄환은 아니다.

  • 실무: 2가지 사이의 줄다리기

    • 지속가능한 소프트웨어의 품질 vs 기술부채를 안고 가는 빠른 결과물

    • 대부분의 회사는 돈을 벌고 성장해야하고 시장에서 빠르게 살아남는 것이 목표

    • 이런 경우에도 클린코드를 추구하지 말라는 것이 아니라 미래시점에 잘 고치도록 할 수 있는 코드센스가 필요하다. 결국은 클린코드의 사고법을 기반으로 결정하는 것

  • 모든 기술의 방법론은 적정 기술의 범위 내에서 사용

    • ex) 당장 급하게 배포나가야 하는데 동료에게 style관련된 리뷰를 주고 고치도록 강요하는 사람

  • 도구라는 것은 일단 그것을 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할때도 아는 법이다.

    • 적정 수준을 알기 위해 때로는 극단적인 시도도 필요하다.

       

이것을 보고 미션때 오버 엔지니어링을 해보는것도 좋은 경험이 되었다고 다시 느끼게 되었다.

📚 기술부채란?

시점에서 오래 소요될 있는 나은 접근방식을 사용하는 대신 쉬운(제한된) 솔루션을 채택함으로써 발생되는 추가적인 재작업의 비용을 반영하는 소프트웨어 개발의 관점

마무리하며

드디어 해당 강의가 마무리 되었다. 여기서 가장 핵심은 추상이다. 우리는 또한 추상과 구체를 인식할 수 있다. 김창준님께서 집필하신 '함께자라기'라는 책을 보면 알듯이 추상과 구체를 넘나들어야 한다. 때로는 bottom-up 때로는 top-dowon을 사용하면서 추상적인 시각과 구체적인 시각을 자유롭게 사용해보고 조금 더 읽기 쉽고 좋은 코드를 작성하는 개발자가 되어야 겠다는 생각이 들었다.

Day4 공통 피드백

내 코드가 예시로 나왔다 우빈님께서 해주신 말씀은 아래와 같았다.

1. boolean으로 return하고 있는 메서드에 예외를 발생시키는데 시도는 좋으나 항상 메서드의 사용현황을 파악 후 상황에 맞게 리팩토링을 하는 것이 좋다. 또한 예외 던지는 것은 비싸기에 항상이 아닌 신중하게 하시라고 조언을 주셨다.

2. 추출한 메서드의 static 키워드가 존재한다면 인텔리제이 IDE에서 메서드 추출을 하면 자동으로 붙기에 알아서 제거해줘야 한다.

3. 상황에 맞게 적절한 수준의 리팩토링이 좋다. 너무 자세히 가면 오버 엔지니어링이 된다.

Day7 코드리뷰

내가 만든 코드에서 StudyCafeConfigProvider라는 객체를 만들고 사용중이였는데 아래와 같이 이 안에는 전부 static 메서드만 있었다.

public class StudyCafeConfigProvider {

    private static final StudyCafeConfig CONFIG = new StudyCafeConfig();

    public static InputHandler getInputHandler() {

        return CONFIG.getInputHandler();

    }

    public static OutputHandler getOutputHandler() {

        return CONFIG.getOutputHandler();

    }

    public static StudyCafeSeatReadProvider getStudyCafeSeatReadProvider() {

        return CONFIG.getStudyCafeSeatReadProvider();

    }

    public static StudyCafeLockerReadProvider getStudyCafeLockerReadProvider() {

        return CONFIG.getStudyCafeLockerReadProvider();

    }

    public static Map<StudyCafePassType, StudyCafePassType> getStrategyMap() {

        return CONFIG.getStrategyMap();

    }

    public static Map<StudyCafePassType, LockerPolicyType> getLockerPolicyMap() {

        return CONFIG.getLockerPolicyMap();

    }

}

이런 경우에는 private constructor를 만드는것이 좋다고 SonarLint에서 알려준다. 하지만 내 인텔리제이에서는 SonarLint를 적용되어 있지만 경고가 따로 뜨지 않았는데 이 부분은 한번 자세히 살펴봐야겠다.

자세한 리뷰 및 후기는 추가 포스팅을 하여 정리해봐야겠다.

후기

이번 주도 금방 지나갔다. 리뷰를 들으면서 많은 고민과 생각이 들었다. 또한 다른 러너분들의 코드를 보면서 신박한 생각과 좋은 점들이 눈에 보이기 시작했다. 우빈님이 주신 피드백과 다른분들의 코드중에 좋은 점들을 채택해서 더 좋은 코드들로 한번 리팩토링을 다시금 해봐야겠다. 다음주부터는 테스트 강의의 시작이다. 테스트도 조금은 걱정이 되지만 열심히 해서 조금 더 성장하는 주가 되었으면 하는걸로 마무리를 지어보겠다.

댓글을 작성해보세요.

채널톡 아이콘