아직 미션을 진행한지 3일째가 되는 날이지만, 고민한 부분들이 여러개가 있어 추후에 기록의 목적으로라도 남겨 놓는다!
💥 이번 주차 코드 내용이 소량 담겨 있으니 7기 프리코스 참여자분들은 읽지 않는 것을 권장드립니다.. 💥
객체는 자유롭다
이번 미션을 들어가기 전에 고민을 많이 했다. 설계를 촘촘히 하고 코드를 짤 것인가, 우선 구현을 해보며 감을 잡을 것인가.
나는 후자를 선택했다. 틀을 완벽히 잡고 작업에 들어가면 물론 더 좋겠지만, 이가 현실적으로 가능한가 하는 생각을 했다.
그리고 책 <객체지향의 사실과 오해> 에서 나온 다음 문장을 나는 계속해서 떠올렸다.
어떠한 객체도 섬이 아니다.
'완벽한' 설계를 한 뒤에 코드를 짜게 되면 객체들이 섬이 되는 것 같다는 생각을 많이 했던 것 같다.
객체들은 서로 협력하기 위한 존재이다. 우선 일을 시작해봐야, 서로 어떤 역할을 하며 협력할지 알 수 있을 것 같다는 생각을 했다. 그래서 일단은 가장 큰 틀만 잡고 객체에 대해서는 코드를 짜며 생각하기로 한 뒤 작업에 들어갔다.
이 때, 나는 객체를 섬으로 만들지 않기 위해 기능 명세서에서 약간의 변화를 줬다.
내가 의도한 변화는 주어를 주는 것이었다. 저번 주차의 기능 명세서에서 등장하는 입력값을 변환하기, 계산하기의 주어는 마치 사람같다. 구현을 하고 있는 '나'의 입자에서 기능 명세서를 작성했었다. 그러나 이번 주차에는 '자동차', '숫자', '사용자' 와 같이 주어를 부여했다.
객체지향의 사실과 오해에서 소프트웨어 세계와 현실 세계의 가장 큰 차이점은 객체들이 자유롭다는 것이다.
'커피' 라는 객체는 사람에 의해서 '마셔지는' 것이 아니라 알아서 만들어지고, 양이 줄어들기도 한다.
이러한 부분을 참고해 자동차가 우승할 수 있고, 숫자가 무작위로 만들어질 수 있다는 현실 세계에서는 어찌 보면 불가능한 얘기들을 나열해 구현을 시작했다.
기능 명세서는 실제 구현이 들어가기 전에 작성하기 때문에 의도가 바뀌어 '사용자'를 정의하게 되지는 않았지만 기능의 중심을 개발하는 내가 아닌 객체로 바꿔 생각한 것만 해도 큰 도움이 되었다! 앞으로도 이러한 방식을 적용해 과제를 풀어 나가볼까 한다.
무조건 상수로 정의하는 것이 옳을까?
상수에 대한 이야기를 많이 봤다. 나의 리뷰에서도 위의 delimiters 에 정의된 , 와 : 를 왜 상수로 정의하지 않았냐는 피드백을 받았었다.
또한 이전 게시글에서 언급했듯, 에러 메시지를 상수화하지 않은 것에 대해서도 질문이 있었다.
내가 당시 상수로 정의하지 않았던 이유는 여러번 사용되지 않으면 상수로 정의할 필요가 없다는 생각을 했다.
또한 ',' 와 ':' 의 경우에는 쌩 문자열로 존재하는 것이 더 직관적이라는 생각도 했었다. 이후에 다른 분들의 코드를 보고, 내가 받은 피드백을 종합적으로 살펴본 뒤에 나의 고유한 기준이 생겼다.
추후에 변경될지도 모르겠지만, 우선 현재의 판단 기준을 기록을 위해 남겨두겠다!
상수는 왜 사용할까?
일단, 기준을 정립하기 이전에 왜 상수를 사용해야 하는지를 살펴보겠다.
1. 가독성 향상
코드의 의도를 명확하게 드러내 혼란을 줄인다.
2. 유지보수성 개선
여러 곳에서 사용되는 경우에 나중에 수정할 때 모든 곳을 찾아 변경해야 한다.
상수를 사용하게 되면 한 곳에서만 수정하면 되기에 더 안전하다.
3. 코드의 재사용성 증가
여러 곳에서 재사용이 가능하다.
언제 상수를 사용하기로 결정했을까?
✅ 에러 메시지 정의 시에
현재로서는 에러 메시지를 상수로서 사용하고자 한다. 이전 게시글에서 언급했듯, 에러 메시지를 상수로 정의하게 되면 일종의 문서와 같은 역할을 해 해당 서비스에서 어떤 경우를 예외 상황으로 규정하는지 쉽게 알 수 있겠다는 생각을 했다.
뿐만 아니라 이번 과제에서는 테스트 코드가 추가됐다. 에러 메시지를 검증할 때 상수로 정의되어 있어야겠다는 생각을 했다.
문자열은 완전히 똑같아야 실패하지 않는데, 테스트 코드에서 의미 없는 문자열의 차이로 인해 테스트가 실패하는 상황을 방지하고 싶기 때문이다.
또한, enum 보다는 상수가 나을 것으로 생각했다. enum 의 경우에는 에러 메시지를 가져오기 위해서는 결국 또 getter 를 거쳐야 하기에 코드 상의 가독성이 떨어진다는 생각을 했다.
언제 상수를 사용하지 않기로 결정했을까?
✅ 상수로 정의하지 않더라도 맥락을 전달할 수 있는 경우
앞서 봤던 예시를 다시 보겠다. ',' 와 ':' 를 상수로 정의한다면 뭐가 될까? COMMA, COLUMN? 이것이 가독성을 향상 시켜줄까?
혹은 기존부터 정의되어 있던 구분자이니 BASIC_DELIMITER 와 같은 네이밍이 가능할지도 모르겠다. (이렇게 된다고 하더라도 , 와 : 의 네이밍이 애매하다. BASIC_DELIMITER_FIRST? BASIC_DELIMITER_COMMA? 둘 다 별 효과가 없다고 생각했다.)
또한 이미 구분자는 리스트 내부에서 관리되고 있다. 이 리스트 밖으로 나와서 문자열 자체만으로 사용될 일은 없다는 것이다. 즉, 변경 지점은 하나로 줄여진다. 더욱이 리스트의 이름이 delimiters 이므로 이미 해당 문자열들이 구분자라는 맥락은 전달하고 있다.
그렇기에 상수를 사용하지 않는다고 해서 1. 가독성을 해치지도 않고 2. 리스트 내부에서 관리되고 있으니 변경 지점도 하나이며 3. 구분자라는 맥락을 전달하기에 이런 경우에는 상수로 굳이 정의하지 않아도 된다고 생각했다.
✅ 비지니스 로직과는 상관 없는 경우
위는 '진승희 : --' 와 같이 이름, 그리고 이동한 거리를 출력하는 메서드이다. 위와 같은 문자열은 상수화하기보다는 그대로 두는 게 오히려 가독성을 해치지 않을 것이라고 봤다.
나는 상수의 장점에서 정의하는 '재사용성' 에 대해서도 다시 정의가 필요하다는 생각을 했다.
위의 코드를 예시로 들어보자면 '-' 가여러 군데에서 사용된다고 해서 한꺼번에 변경될 일이 있을까? '-' 는 단순히 출력에 사용된다. '-' 가 다시 사용된다고 하더라도 위와 같이 '자동차가 움직이는 모습을 나타낸다'는 맥락을 공유하며 재사용되는 것이 아닐 것 같다는 이야기다. 이러한 경우에는 '-' 문자열을 또 다시 사용할 일이 있다고 하더라도 정말로 상수의 장점에서 이야기 하는 '재사용' 이라고 할 수 있을까? 싶었다.
'이번주부터 저희 회사에서는 자동차의 움직임을 나타낼 때 - 대신 무조건 + 를 사용하겠습니다!' 하고 공표할 일은 없지 않을까?
다시 말해, '-' 를 상수로 정의하고 재사용한다고 하더라도 이가 같은 맥락을 공유하며 재사용되지는 않을 것 같다는 것이다.
그러나 다음과 같은 경우는 어떨까?
계속해서 이야기한 예시들과 같이 // 와 \\n 역시도 단순 문자열이다. 그러나 // 는 이 서비스의 맥락에서 단순한 의미를 갖지 않는다.
사용자가 정의하는 커스텀 문자열의 시작을 알려준다. 그리고 구분자는 이 계산기 서비스 내에서 의미를 갖는다.
따라서 이 경우에는 상수로 정의하는 것이 의미가 있다고 생각했다.
메서드는 얼마나 작게 정의해야 할까?
흔히 메서드의 책임을 적절히 분배하라고 한다. 하나의 기능을 하나의 메서드가 하는 게 좋다는 것이다.
public void temp(int input) {
if (isBiggerThanZero(input)) {
// do something ...
}
}
private boolean isBiggerThanZero(int input) {
return input > 0;
}
위는 마음대로 작성해 본 코드이다. 나는 위와 같은 상황에서 이렇게까지 촘촘하게 분리해야 할까 하는 고민을 했다.
isBiggerThanZero 메서드에서는 return 문에서 단순히 파라미터 값이 0 보다 큰지 판단한다.
public void temp(int input) {
if (input > 0)) {
// do something ...
}
}
만약 위의 코드가 이처럼 isBiggerThanZero 가 사라지고 temp 의 if 문으로 들어간다고 가정하면 가독성이 해쳐지는가? 나는 아니라고 봤다. 오히려 isBiggerThanZero 와 같은 말로 풀어 쓴 메서드명보다는 수식이 더 명확하게 와닿는다.
그렇다면 temp 메서드가 너무 많은 역할을 하는가? 이에 대한 고민을 좀 했었다.
나는 아니라고 생각했다. input 이 0 보다 큰지 판단하는 로직까지 temp 메서드에 맡기는 것이 아니라, 이는 java 에 내장된 비교 연산자가 하고 있다고 판단했다. 즉 이미 적절한 메서드에 책임을 넘긴 것과 마찬가지라고 생각했다. 그렇기에 temp 가 감당하고 있는 역할이 아니라고 봤다.
그렇다면 단순히 비교 연산자가 사용된다고 해서, return 문 한 줄로 표현할 수 있는 간단한 로직이라고 해서 메서드 분리를 하지 않아도 될까?
public static void validateNameOfCars(String input) {
if (input.isBlank()) {
throw new IllegalArgumentException("자동차의 이름은 빈 문자열이어서는 안됩니다.");
}
if (input.replace(DELIMITER, "").isBlank()) {
throw new IllegalArgumentException("자동차의 이름은 구분자인 쉼표(,)로만 이루어질 수 없습니다.");
}
if (input.endsWith(DELIMITER)) {
throw new IllegalArgumentException("자동차의 이름은 구분자인 쉼표(,)로 끝나서는 안됩니다.");
}
}
나는 이 역시도 아니라고 봤다. 첫번째 if 문에서는 input 이 isBlank 인지 확인한다. 그리고 마지막 메서드에서는 input 값이 DELIMITER 로 끝나는지 확인한다. 정말 직관적이다. 그러나 두번째의 경우에는 무엇을 검증하는지 와닿지가 않는다.
public static void validateNameOfCars(String input) {
if (input.isBlank()) {
throw new IllegalArgumentException("자동차의 이름은 빈 문자열이어서는 안됩니다.");
}
if (containsOnlyDelimiter(input)) {
throw new IllegalArgumentException("자동차의 이름은 구분자인 쉼표(,)로만 이루어질 수 없습니다.");
}
if (input.endsWith(DELIMITER)) {
throw new IllegalArgumentException("자동차의 이름은 구분자인 쉼표(,)로 끝나서는 안됩니다.");
}
}
private static boolean containsOnlyDelimiter(String input) {
return input.replace(DELIMITER, "").isBlank();
}
이렇게 메서드로 빼면 어떨까? input 값이 delimiter 만 포함하고 있는지 확인한다는 맥락이 직관적으로 전달된다.
즉, input.isBlank()나 input.endsWith(DELIMITER), input > 0 와 같이 특별한 조작 없이 + 다른 맥락을 갖고 있지 않은 경우에는 굳이 함수로 분리하지 않아도 된다는 생각을 했다. 그러나 containsOnlyDelimiter 와 같이 단순 조작을 넘어서서 비지니스 상에서 부가적인 맥락을 갖고 있는 경우에는 메서드로 분리해야겠다고 결론을 내리게 됐다.
사실 이 부분은 여전히 확신이 서지는 않아서, 디스코드에 다른 분들의 의견을 여쭤봤다. 👀
변경은 어디까지 고려해야 할까?
사실 이 부분은 여전히 너무 헷갈리는 것 같다. 이와 관련된 자료를 찾아보니 YAGNI (You Ain't Gonna Need It) 라는 소프트웨어 개발의 원칙이 있다고 한다. 추후에 필요할 것을 고려해서 미리 너무 많은 것들을 만들어 놓지 말라는 것이다.
최근 테스트 관련 책을 읽고 있는데, 모든 해피 케이스 (정상 작동하는 경우) 를 파악하지 못했다고 해서 구현을 멈추지는 말라고 한다. 추후에 추가하거나 수정을 하면 되기 때문이다. 또한, 어떤 변경이 발생하게 되면 오히려 이전의 테스트 코드들이 가이드라인이 되기에 변경 가능성이 있는 코드를 남겨둬도 괜찮다고 한다. 그 당시에 최선의 선택를 하는게 중요하다는 것이 핵심인 것 같다.
변경을 고려해서 지금 상황에서 나중에 이런 것들이 추가될 수도 있으니까.. 하고 생각하기 보다는 변경이 발생했을 때 유연하게 대처할 수 있는지를 보는 게 더 맞는 것 같다. 테스트 뿐만 아니라 코드의 구현 자체도 이런 맥락이 아닐까.. 예측만 해본다. 아직 이 부분에 대한 것은 너무 어려운 것 같다.
많은 고민과 생각을 해본 이틀이었다.. 내가 목표한 깊게 고민해서 나만의 가이드라인과 이유를 만들어야겠다는 목표에 점점 가까워져가고 있는 것 같아 프리코스가 끝난 뒤 나의 모습이 궁금하다!
내일의 목표는 조금 더 엣지 케이스들에 대해 깊이 고민해보고 처리하는 연습을 해보고 싶다.
그럼 내일도 힘내자~
'회고' 카테고리의 다른 글
[우아한 테크코스 7기] 프리코스 2주차 다섯째, 여섯째날 회고 - 유연하게 유연하게.. 🏄🏼🌊 (1) | 2024.10.28 |
---|---|
[우아한 테크코스 7기] 프리코스 2주차 넷째날 회고 (0) | 2024.10.25 |
[우아한 테크코스 7기] 프리코스 2주차 첫번째 회고 - ✏️ 리뷰를 해보자 (1) | 2024.10.24 |
[우아한 테크코스 7기] 프리코스 1주차 회고 (2) | 2024.10.21 |
좋은 팀장이 되기 위한 고민들 (3) | 2024.04.14 |