우아한 테크 코스 5기 프리코스 - 3주차 회고

경쟁력있는 개발자


1. 나의 부족한 마인드

1. 모순적인 마인드

지난주 코수타를 참여하면서 ‘경쟁, 비교 의식’으로 인해 많은 사람들에게 ‘자존감 하락’이 나타난다는 것을 알았다. 특히나 프리코스는 다른 사람들의 PR을 통해 코드를 전부 볼 수 있기 때문에, 더 그러한 증상들이 생기는 것 같다. 심지어 우테코에 들어가고 나서도 그런 증상으로 인한 상담이 많이 이루어진다고 하니, 말 다했지 싶다.

난 그 마음이 전적으로 이해가 된다. 이유는 나 또한 그런 증상이 오는 경우가 많기 때문이다. 나도 프리코스 2주차까지는 미션 제출이 전부 끝난 뒤 다른 사람들의 PR을 보면서 ‘난 왜 저런 방법을 생각하지 못했을까??’와 같은 생각이 들 때도 많았다.

뿐만 아니라 토론과 피어 리뷰같은 경우도, 내 미션을 하기 급급해서 찾지 않는 경우가 많았다. 그거 할 시간에 미션 구현 코드를 한 번이라도 더 살펴보는 것이 좋다는 마인드가 강했던 것 같다.

난 분명 우테코에 들어가고 싶었던 이유 중 가장 큰 이유가 ‘열정있는 개발자들과 함께 토론하면서 성장하고 싶다.’라는 것이었다. 그러나 이 이유와 대조해서 비교해 봤을때, 이는 분명 모순적인 사고방식과 행동이다.


정말로 그런거 할 시간에 내 미션 코드를 한 번 더 살펴보는 것이 내 실력 향상에 더 좋은가??

프리코스 3주차 미션이 끝난 후인 지금은, 내가 이 질문을 받는다면 “절대 아니다.”라고 단정지어 말할 수 있다. 그 이유에 대해 간단히 정리해 보고자 한다.


2. 경쟁력 있는 개발자에 대한 잘못된 인식

경쟁력 있는 개발자에게는 어떤 요소들이 필요할까. 물론 하드 스킬(담당 업무를 수행하기 위해 필요한 실질적인 기술)도 정말 중요할 것이다. 그러나 그것만큼이나 중요한 요소가 또 있다. 바로 ‘커뮤니케이션’ 능력이다. 이를 흔히 소프트 스킬(소통 능력, 실행력, 리더십 등 대인 관계와 관련된 정서적 능력)이라고 부르는 듯 하다.

지금까지 프로그래밍 공부를 하면서 많이 접한 얘기가 있다.


개발은 혼자 하는 것이 아닌, 팀단위로 하는 일이다.

그렇기 때문에 개발자에게 ‘소프트 스킬’은 ‘하드 스킬’만큼이나 혹은 그보다 더 중요한 요소이다.

소프트 스킬은 ‘하드 스킬을 효율적으로 활용할 수 있도록 도와주는 능력’이다. 여기서 ‘하드 스킬을 효율적으로 활용할 수 있게 한다’는 의미는 여러가지로 해석될 수 있다고 생각한다.

  1. 개발자간의 정확한 소통이 이루어지면서 코드의 품질이 향상된다.
  2. 팀원간의 마찰과 불화 없이 부드럽고 원활한 토론이 이루어지면서 코드의 품질이 향상된다.
  3. 서로간의 코드를 리뷰해주면서 피드백을 받은 코드의 품질이 향상된다.
  4. 서로간의 코드를 리뷰해주면서 리뷰어의 실력도 같이 향상되고, 그에 따라 리뷰어의 코드 품질도 향상된다.
  5. 팀장의 좋은 리더십으로 인해 활발한 토론과 코드 리뷰 문화가 생기고, 그로 인해 코드의 품질이 향상된다.

등등…

이보다도 훨씬 더 많은 이점이 있다고 생각한다. 정말로 경쟁력 있는 개발자가 되고 싶다면, ‘과거의 나보다 나아지는 것에 몰입하되, 비교 의식보다는 함께 성장하는 것에 초점을 둔 마인드’가 필요하다고 생각한다. 진짜 백번 양보해서 내 실력만을 생각하는 이기적인 생각을 갖고 있다고 해도, 이 소프트 스킬의 중요성은 부정할 수 없을 것이라 생각한다.

지난주의 코수타에 참여하면서, 내 모순적인 마인드에 대해 뼈를 맞는듯한 느낌을 받고, 3주차 미션을 진행하기 전에 2가지 원칙을 세우게 되었다.

  1. 미션 구현시간 중 일부의 시간을 따로 떼서라도 토론과 피어 리뷰에 적극 참여하자.
  2. 남의 코드와 비교하며 한탄할 시간에, 지난주의 나보다 나아지기 위해 끊임없이 코드에 몰입하고 탐구하자.


실제로 3주차 미션 기간동안 매일매일 지난 주차 미션의 피어 리뷰를 통해 리뷰와 토론을 진행했다. 그 과정에서 다른 사람이 적은 리뷰 내용들과 코드를 보고 토론을 하면서 내가 생각하지 못한 방향으로 생각을 확장해보고, 막연하게 생각하고 있던 부분을 더 명확하게 알기 위해 근거를 찾아보고, 이러한 경험들을 통해 실질적으로 실력향상이 되는 것을 경험하게 되었다.

그냥 ‘제삼자가 이런 마인드를 갖는 것이 좋아’ 라고 말한 것을 듣고 ‘그냥 그런가보다’ 라고 머리로만 아는 것이 아니라, 실제로 왜 이런 마인드가 더 중요한지 체감을 하게 되는 귀중한 기간이었다.

사실 이번 주 미션을 진행하면서 느낀 점과 배운 점은 디테일하게 들어가면 굉장히 많지만, 그 중 가장 인상 깊었던 부분 몇 가지만 적어보려 한다.


2. 객체 지향의 클래스 분리와 오버 엔지니어링

미션을 구현하면서 ‘클래스를 분리하면서 이런 것까지도 분리하는 것이 좋은가??’ 라는 의문점을 갖게 만드는 부분이 몇 가지 있었다. 그중 대표적인 부분 하나만 예시를 들어보자.

먼저 우테코에서 미션 시작 전부터 어느정도 구현을 해놓고 제공한 로또 클래스이다. 이 클래스를 토대로 3주차 미션을 구현해야했다.

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
        validate(numbers);
        this.numbers = numbers;
    }

    private void validate(List<Integer> numbers) {
        if (numbers.size() != 6) {
            throw new IllegalArgumentException();
        }
    }

    // TODO: 추가 기능 구현
}


여기서 4가지의 조건도 같이 제공되었다.

  • Lotto에 매개 변수가 없는 생성자를 추가할 수 없다.
  • numbers의 접근 제어자인 private을 변경할 수 없다.
  • Lotto에 필드(인스턴스 변수)를 추가할 수 없다.
  • Lotto의 패키지 변경은 가능하다.

일단 numbers 인스턴스 변수의 타입을 변경할 수 없다는 제한은 없다. 그래서 원래는 numbers 의 각 요소인 로또 번호(Integer)도 LottoNumber 라는 클래스로 분리하려고 했다. 밑의 코드는 미션을 전부 구현한 뒤의 로또 클래스 모습이다.

public class Lotto {
    private final List<Integer> numbers;
    
    public Lotto(List<Integer> numbers) {
        validate(numbers);
        this.numbers = numbers;
    }
    
    private void validate(List<Integer> numbers) {
        LottoNumbersValidator.validate(numbers);
    }
    
    public int countOfMatchingNumber(final Lotto lotto) {
        return (int) numbers.stream()
                .filter(lotto::isNumberContains)
                .count();
    }
    
    public boolean isNumberContains(final int bonusNumber) {
        return numbers.contains(bonusNumber);
    }
    
    public List<Integer> lottoNumber() {
        return Collections.unmodifiableList(numbers);
    }
    
    @Override
    public String toString() {
        return "Lotto{" +
                "numbers=" + numbers +
                '}';
    }
}


여기서 문제점이 하나 생겼다. 보통 클래스를 나눌 땐, 그 클래스가 명확한 책임을 부여받을 수 있을 때 의미가 있다고 생각한다.

그런데 기능을 전부 구현하고 로또 번호를 클래스로 분리하려고 보니, 분리 후의 LottoNumber 클래스의 책임은 단지 자신의 번호와 다른 LottoNumber의 번호가 다른지 비교하는 일 외엔 없다는 것이다. 그것도 아마 equals() 와 hashcode() 메서드를 재정의하기만 하면 끝나는 것일텐데, 그러면 분리한 의미가 뭐가 있나 싶었다.

로또 번호의 유효성 검증에 대한 책임을 맡기면 되지 않냐라고 생각할 수도 있지만, 유효성 검증은 이미 Validator 클래스들로 전부 분리한 상태였다. 만약 이런 상황에서 로또 번호를 LottoNumber 클래스로 꾸역꾸역 분리한다면, 그것이야말로 ‘오버 엔지니어링’이 아닐까.


3. 단위 테스트의 범위

1. 단위 테스트 작성 시 하위 메서드까지 작성해야 하는가??

이번 3주차 미션을 구현한 실제 코드로 예시를 들기엔 너무 많은 코드를 들고 와야 하기때문에, 예시를 간단히 작성해 보았다.

public class Position {
    private final int position;
    
    public Position() {
        this(0);
    }
    
    public Position(final int position) {
        this.position = position;
    }
    
    public Position increase() {
        return new Position(position + 1);
    }
    
    @Override
    public String toString() {
        return "Position{" +
                "position=" + position +
                '}';
    }
}
public class Car {
    private final Name name;
    private final Position position;
    
    public Car() {
        this.position = new Position();
    }
    
    public Position move() {
        return position.increase();
    }
    
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.move());
    }
}

// 실행 결과
// Position{position=1}


Name 클래스는 이해를 돕기위한 가상의 클래스를 둔 것 뿐이니 신경 안써도 된다.

Car에서 String 타입의 name 과 int 타입의 position 을 클래스로 분리한 후, 각 클래스에게 책임을 위임해주는 모습이다. 그 중 차의 이동 관련 기능만 구현을 해보았다.

이 예시의 상황에서 고민되는 지점이 있다. 차의 이동을 테스트 하는 단위 테스트를, Car의 move() 메서드만 구현해주면 되는 것인가, 아니면 Position 의 increase() 메서드까지 구현해줘야 하는 것인가.

사실 Car의 move() 메서드만 테스트를 해주면 Position 의 increase() 메서드도 같이 테스트가 되는 것이라고 볼 수 있지 않을까. 혹은 Car의 move메서드가 아닌 Position의 increase() 메서드만 테스트 해주면 되지 않을까.


2. 일단 결론은 보수적인 스탠스

위의 예시는 굉장히 간단하게 구현했기에 티가 많이 안날 수 있지만, 만약 저 Position 의 increase() 메서드를 Car 뿐만 아니라 다른 클래스에서도 재활용하는 상황이 온다면 어떨까.

그런 상황에서도 이미 다른 메서드에 의해 같이 테스트 되고 있으니 상관없다고 생각할 수 있는가. 만약 간접적으로만 테스트되고 있는 하위 메서드에서 어떤 버그가 존재했는데, 직접 테스트 되고 있는 상위 메서드의 어떤 특정 로직에 의해 그 버그가 티가 안나게 되는 상황이라면.

프로그래밍은 결국 인간이 하는 것이기에 어떤 변수와 버그가 숨어들어갔는지 처음부터 전부 파악할 수 없다. 그래서 이번 미션에서는 결국 보수적인 스탠스를 취하고, 모든 public 메서드에 단위 테스트를 구현하게 되었다.

물론 여기저기 검색도 해보고 학습도 해본 결과, Mock 프레임 워크와 관련된 얘기가 나오면서 하나의 단위 테스트의 범위를 ‘클래스 혹은 메서드’로 정할 것인지, ‘하나의 행동’으로 정할 것인지에 따라 또 의견이 달라지는 부분도 있었다.

여전히 풀리지 않는 의문점으로 남아있지만, 그러한 부분들을 앞으로 지속적으로 학습해나간다면, 생각을 더 확장해 볼 수 있을거라 생각한다.





© 2021. All rights reserved.

----------Powered by Hydejack----------

Jun's Development Blog