일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 기능명세서
- 플레이 스토어 20명
- 운영체제 #CS지식
- git
- 구글 플레이 비공개 테스트
- 구글 비공개 테스트 20명
- 프리코스
- 객체지향설계
- 객체지향
- 클린코드
- 우테코
- 커밋 메시지
- 플레이스토어 비공개 테스트
- 설계
- 구글 플레이 스토어 배포 방법
GYUD-TECH
클린코드를 연습하는 이유 본문
평소에 클린코드라는 말을 많이 듣긴 하였지만 실제 클린코드를 짜기 위해서 고민 한적은 없다.
대학생때 친구들과 진행한 프로젝트에서는 협업을 하더라도 내가 짠 코드는 나만 보는 경우가 많기 때문에 클린코드의 중요성을 별로 못 느꼈던 것 같다.
우테코 프리코스를 하면서 nextStep 책을 사서 읽게 되었고 제일 앞에 등장하는 극단적인 리팩토링 방법은 충격이였다. 이 방식을 실제로 프리코스에도 적용해 보았고, 클린코드에 대해서 고민하는 시간을 가졌다.
적용했던 클린코드
아래는 1주차 과제에 적용했던 클린코드 포맷이다
1. 한 메소드는 한 개의 역할만 수행하는가?
2. 메소드는 최소한의 들여쓰기를 적용하였는가 (1까지만 허용)
3. 메소드를 10 줄 이하로만 허용하였는가
4. 메소드의 인자 수를 제한했는가 (3개 미만)
5. else 예약어를 쓰지 않았는가
6. 3개 이상의 인스턴스 변수를 가진 클래스를 구현하지 않았는가
7. 모든 원시값과 문자열을 포장하였는가
8. 코드 한줄에 점을 하나만 사용하였는가
9. 매직 넘버, 매직 리터럴을 사용하지 않았는가
10. 변수와 메소드 명을 명확하게 작성하였는가
1번에서 5번까지의 조건들은 공통적으로 한개의 메소드는 한개의 역할만 수행한다는 것이였다. 한개의 메소드는 한개의 역할만 수행하도록 코드를 구현하다보니 2번에서 5번의 조건들은 자연스럽게 지킬 수 있었다.
6번(3개 이상의 인스턴스 변수를 가진 클래스를 구현하지 않았는가)을 적용하다 보니 Computer 객체를 Result, Answer 객체로 분리하였고 Player 객체를 RestartFlag, Guess 객체로 분리하게 되었다. 그리고 분리한 Answer, RestartFlag, Guess 객체에 7번의 포맷을 적용하여 다른 객체에서도 메소드를 호출할 필요 없이 생성만 하면 값의 유효성을 검증할 수 있도록 하였다. (사실 1번 과제에서는 Computer와 Player만 객체만 다른 객체를 호출한다)
8번(코드 한줄에 점을 하나만 사용하였는가)은 객체의 협력관점에서 명확하게 협력관계를 보이기 위한 것이라고 생각하였다. 지키기 위해 노력하였지만 아래의 코드와 같은 controller 역할을 담당하는 Computer 객체에서도 지켜야 하는지에 대한 의문이 들었다.
Result result = new Result(player.makeGuess, answer);
String resultString = result.toString();
위 코드에서 result라는 변수를 생성하지 않고 아래와 같이 한줄로 작성할 수 있다.
String resultString = new Result(player.makeGuess, answer).toString();
사실 String이 아닌 Result 객체는 toString 함수를 제외하면 외부에서 전혀 호출을 하지 않기 때문에 처음부터 resultString으로만 받아도 된다고 생각하였다. 위의 축약된 코드에서도 "player의 guess를 받아서 result를 만들고 문자열로 반환한다"는 의미를 명확하게 전달할 수 있다.
하지만 객체를 생성하자마자 객체를 가리키는 변수를 할당하지도 않고 메소드를 호출하는 것은 객체를 한번 쓰고 버리는 것이라고 생각되어서 객체를 받는 코드로 나누었다. 이 과정에서 과연 Result를 객체로 둘 필요가 있었는가에 대한 의문도 들었다.
아직 Result를 객체로 두어야 하는가에 대한 명확한 답은 모르겠다. 여러번 호출할 필요가 없는 값들을 굳이 객체로 두어야 하는지에 대해서 2주차에서 더 고민해 볼 것이다.
변수와 메소드의 의미를 명확하게 드러내기 위해서는 9번과 10번이 매우 중요하다고 생각한다. 최대한 의미를 잘 드러내도록 작성하였지만 아직 부족한 것 같아 2주차에서는 변수명과 메소드명을 짓는 방법을 공부하고 더 명확하게 짓도록 노력할 것이다.
적용하지 않았던 클린코드
아래의 클린코드 양식은 사용하는게 좋은것인지에 대해 의문이 들어 사용하지 않은 포맷이다.
1. getter와 setter 없이 구현하였는가
2. 중복되는 예외 처리를 한곳에 모았는가
3. 상수를 한곳에 모아서 처리하였는가
setter는 사용하지 않았지만 getter는 객체의 값을 가져오기 위해서 사용하였다. private으로 설정한 변수를 getter가 없으면 다른 클래스에서 접근할 수 없기 때문에 getter를 사용하였다. getter를 사용하지 않고도 다른 대안은 어떤 것들이 있는지 더 찾아볼 예정이다.
중복되는 예외 처리를 한곳에 모아서 처리했는지는 내가 코드를 보면서 든 의문이다.
public Guess(String playerInput) {
checkNullInput(playerInput);
List<Integer> playerInputList = strToIntegerList(playerInput);
checkInvalidLength(playerInputList);
checkInvalidInput(playerInputList);
checkDuplicateDigit(playerInputList);
guess = playerInputList;
}
private void checkNullInput(String playerInput) {
if (playerInput == null) {
throw new IllegalArgumentException();
}
}
private void checkInvalidLength(List<Integer> playerInputList) {
if (playerInputList.size() != VALID_NUMBER_LENGTH) {
throw new IllegalArgumentException();
}
}
private void checkInvalidInput(List<Integer> playerInputList) {
for (int digit : playerInputList) {
checkInvalidDigit(digit);
}
}
private void checkInvalidDigit(int digit) {
if (digit < MIN_ALLOWABLE_DIGIT || digit > MAX_ALLOWABLE_DIGIT) {
throw new IllegalArgumentException();
}
}
위 코드는 내가 작성한 Guess 클래스인데 클래스의 모든 기능이 playerInput에 대한 유효성 체크와 예외처리 부분이다. 예외를 처리하는 부분과 비즈니스 로직이 함께 섞여 있는것이 가독성을 해칠 수도 있겠다고 생각하여 예외 처리 부분을 별도로 분리하여 모아놓는 방식에 대해서 고민하였다.
관련 자료를 찾아보았을 때 예외 처리 구문을 한 곳으로 모아서 처리 하는 방법이 있었지만 적용하지 않았다. 이유는 명확한 의미전달을 해친다고 판단했기 때문이다. Guess에 대한 예외를 처리하는 코드가 객체 외부에 있으면, 예외 처리의 의미를 불분명하게 만든다고 생각하였다. 다른 코드에서 예외 처리하는 형식이 똑같다면 코드의 중복 제거를 위해 모아서 처리하는 것이 좋다. 하지만, Guess 클래스의 예외 처리는 checkNullInput을 제외하면 모두 Guess 클래스에서만 사용하기 때문에 클래스 내부에서 처리하는 것이 좋겠다고 판단하였다.
상수를 처리하는 방식에서도 예외 처리와 똑같은 고민을 하였다.
public class Result {
private static final int INITIAL_COUNT = 0;
private static final int START_INDEX = 0;
private static final int NUMBER_LENGTH = 3;
private static final int CONTAINED_VALUE = 1;
private static final int NOT_CONTAINED_VALUE = 0;
private static final int MATCHED_VALUE = 1;
private static final int NOT_MATCHED_VALUE = 0;
private static final String BALL = "볼";
private static final String STRIKE = "스트라이크";
private static final String NOTHING = "낫싱";
private static final String BLANK = " ";
private final String result;
위 코드는 Result 클래스의 일부이다. 상수를 너무 많이 선언하다보니 정작 중요한 Result 객체의 메소드들이 강조되지 않았다. 이러한 이유로 상수를 따로 모아두는 것을 고민하였지만, 객체의 상수가 객체 내부에 있지 않으면 의미가 없다고 생각하여 그대로 두기로 하였다.
마무리
클린코드에 대해서 이렇게 오래 고민해 본 적은 처음이다. 클린코드에 대해서 생각하면서 내가 짠 코드의 이유를 하나하나 생각해 보게 되었고 뭐가 더 효율적인지 고민하게 되었다.
내가 정리한 클린코드의 장점은 다음과 같다.
- 가독성이 좋아진다. (협업에 유리)
- 왜 그렇게 짰는지에 대한 이유가 확실해진다
- 유지보수에 용이하다.
이 중에서 특히 내가 왜 이 코드를 짰는지 이유를 명확하게 하기 위해서 다른 대안들을 찾아보고 비교해보는 과정에서 공부가 되는 것 같아서 좋다.
아직 이해할 수 없는 클린코드 작성법도 있었다. 이런 고민들은 아직 클린코드에 대한 이해가 부족하기 때문에 하는 고민일 수 있다. 하지만 클린코드 역시 정답은 아니라고 생각한다. 클린코드 규칙을 지키다 보니 간단하게 짜는 것도 오히려 길고 복잡하게 짜는 것 같은 느낌이 들기도 하였다. 무작정 클린코드를 받아들일게 아니라 왜 필요한지 이유를 생각해 볼 필요가 있다. mvc 패턴이나 객체지향 프로그래밍도 역시 마찬가지이다. 상황에 따라서 뭐가 효율적일지를 판단해야 한다.
클린코드를 짜보지 않으면 클린코드의 장단점을 알 수 없다. 우테코에서는 내 코드의 클린함을 판단할 충분히 고민할 시간을 주기 때문에 클린코드를 연습하기 좋은 기회이다.
앞으로
클린코드를 연습하는 과정이라고 생각하고 극단적으로 클린코드의 규칙을 지켜 볼 것이다. 이를 통해 상황에 맞는 코드를 짤 수 있는 능력을 기르고 싶다.
'기술-개발' 카테고리의 다른 글
단위 테스트를 작성하며 객체지향 적용하기 (0) | 2025.01.27 |
---|---|
Stream 적용기 (0) | 2023.10.30 |
좋은 커밋 메시지 작성법 (1) | 2023.10.25 |
기술명세서 작성법 (1) | 2023.10.24 |