💻 Backend
Clean Code Of Mine
이 글에는 전제 조건이 있다.
극단적인 방향으로 가지 않는 것!
클린 코드 뿐만 아니라 유명한 개발자가 자신의 경험을 빗삼아 쓴 글의 마지막에는 항상 “이 것이 정답이 아닐 수 있습니다.”라는 뉘앙스의 말이 있다.
즉, 그때는 맞았고 지금은 틀리다는 말처럼 이 또한 달라질 수 있다.
게다가 언제나 상황에 맞춰 더 나은 코드를 구현하는 것이 중요하다. 정답을 찾는 것 보단.
핵심은 스스로 생각하기. 무조건 옳다고 믿지 않기.
이 글은 클린 코드를 빙자한 내 경험을 담고 있다.
다시 말해서 내가 판단하는 클린 코드다.
2장 : 의미 있는 이름
코드도 언어이기 때문에 가장 기초는 작명이다.
대략 목차를 보면 아래와 같다.
- 의도를 분명히 밝혀라
- 그릇된 정보를 피하라
- 발음, 검색하기 쉬운 이름을 사용하라.
- 자신의 기억력을 자랑하지 마라.
- 기발한 이름은 피하라.
- 한 개념에 한 단어를 사용하라.
- 말장난을 하지 마라.
인천에서 출발해서 부산까지 이르는 자전거길을 탐색하려고 한다.
fun findRoad() {
val start = "인천"
val dest = "부산"
val way = "자전거"
easyWayToGo(start, dest, way)
}
누군가 easyWayToGo 라는 함수를 정의해서 사용했다.
직역하면 ‘쉬운 길’이라는 뜻이다. 그러나 쉬운 길은 너무 추상적인 표현이다.
→ 언덕이 아닌 지형, 횡단보도가 적은 길, 내리막이 많은 길, 울툴불퉁하지 않은 길,,,
이 중에 어느 것도 쉬운길이 아닌 게 없으니 말이다.
결론은 한 개념에 한 단어를 사용하란 것이다.
쓸데없이 재밌는 표현을 떠올릴 필요도 없고 그냥 지금 내가 하려고 하는 것의 가장 작은 단위로 이름을 작성하면 2장: 의미 있는 이름은 간단히 지켜질 수 있다.
실제 사례로 보면..
당신이 만드는 서비스에 주니어 회원이 있고 시니어 회원이 있다.
그런데 변수 명, 나아가 도메인 이름을 USER 라고 잡는다면 누구를 의미하는 건지 클래스를 눌러보거나, 앞 뒤 문맥을 파악하려고 애써야 한다.
아주 자연스레 불편하게 만드는 것. 협업에서 좋을 리 없다.
3장: 함수
- 작게 만들고, 한 가지만 해라!
- 서술적인 이름을 써라.
- 인수를 생각해라.
- 예외를 사용하라.
- 부수 효과를 일으키지 마라.
3장도 2장과 같은 결이다.
함수 또한 간단명료하게 만들어라. 이게 3장의 내용이다 🤗
한번 쯤 짚어 볼 만한 내용으로는 파라미터와 리턴타입에 관한 게 있다.
2장에서 사용한 예시를 다시 끌고 와서.
fun findRoad() {
val start = "인천"
val dest = "부산"
val way = "자전거"
easyWayToGo(start, dest, way)
}
당연하게도 다른 로직에서 findRoad() 메소드를 호출한다면 from 어디 to 어디인지 알 수 없다.
fun findRoad(start: String, dest: String, way: String) {
...
}
시작점과 출발점, 그리고 방식을 파라미터로 받는다면 아래 코드 처럼 호출하는 쪽에서 단번에 파악할 수 있다.
fun find() {
...
findRoad(start = "인천", dest = "부산", way = "자전거")
...
}
함수의 이름이라고 하면 findRoad 만 생각하지 말자.
findRoad(args..) : ReturnType 까지가 함수의 풀네임이다.
나아가, 파라미터의 이름과 위치도 신경 써주는 게 좋다.
내가 돈을 입금하려고 하는데
fun inputMoney(b, m)
b와 m이 어떤 의미인지 단번에 파악하기 어렵다.
fun inputMoney(bank, money)
입금하는데 은행과 금액을 적는 것이라는 걸 단번에 알 수 있게 되었다.
물론 여기도 이슈는 있다. 더 정확한 용어를 차용한다면 좋을 것이다.
fun deposit(bank, amount)
동네 ATM에서 영문 버전으로 사용해보면 입금은 deposit 으로 표현하고 금액은 amount 로 표현한다.
한번 더 실제 사례를 보면..
파라미터에 들어갈 내용이 많아지면 마찬가지로 보기 어려워진다.
예를 들어서
fun deposit(bank, amount, date, who ...)
위 코드처럼 쓰이는 모든 값을 다 때려넣으려고 하면 줄바꿈으로 인한 코드 스타일 문제 뿐만 아니라, 호출할 때마다 불편함을 초래한다.
내 경우에 이럴 땐 크게 3가지를 고민해보면 실마리를 찾을 수 있었다.
- 인풋과 아웃풋을 기준으로 이 함수가 가진 단 하나의 역할은 무엇인가
- 여러 역할을 가졌다면 더 자세히 나눠서 함수가 2~3개 늘어나더라도 쪼개보자.
- 다 필요하다면 객체를 만들어서 한번에 넘기자.
- 이 때 객체의 이름은 단순하고 직관적이어야 좋다.
- 나는 보통 코드 스타일을 기준으로 줄바꿈이 일어날 때 (주로 4개 이상) 객체로 넘긴다.
클린 코드를 다 읽고
한 챕터씩 읽으면서 2장, 3장을 기록한 것처럼 글을 쓰기 시작했지만 어느 순간 멈추고 돌아보니 중요한 건 이게 아닌 것 같았다.
남은 내용들도 모두 개발자로서 협업에 도움이 되는 팁으로 가득 차 있다.
내가 정리해야 할 건 그 팁 목록이 아니라 경험에서 더 좋다고 느꼈던 코드들인 것 같았다.
그래서 3장 이후의 기록은 모두 지우고, 다시 쓴다.
이 글은 과거 내가 남긴 후기다. (블로그를 자주 바꾸곤 했다)
지금은 3년차를 넘어가기 시작했고 몇몇 유명 IT기업이 지향하는 코드를 직/간접적으로 겪어보기도 했다.
그리고 지금은 이렇게 생각한다.
코드를 적으며 깊이 봐야할 방향은 크게 3가지다.
- 협업의 관점
- 효율의 관점
- 안전의 관점
협업의 관점!
동료를 너무 믿어선 안된다.
대뜸 부정적인 말을 하게 됐지만, 코드 리뷰 상황 속에서 하는 말이다.
(내 동료는 언제나 최고이고 함께 멋진 서비스를 만들어 갈 것이다.)
동료를 믿어버리면 리뷰에서 놓치는 게 생긴다.
“어차피 잘 하는 사람이니까” - “대충봐도 잘 했겠지” - “보기 귀찮아”
또는 “지금 좀 바쁘니까”
내가 쓴 코드만 내 코드인 게 아니다.
내가 보고 통과시킨 코드도 내 코드다.
이 코드가 결국에 서비스의 한 조각이 되고, 이로 인해 버그가 생긴다면 그건 작성자의 탓이 아니라 모두의 탓이다.
특히 촉박한 시간에 쫓긴다면 차라리 다른 동료의 리뷰를 기다리자.
전체를 읽지 못하고 허겁지겁 승인을 눌렀을 때 바로 머지 단계로 넘어가게 될 수 있으니 말이다.
그리고 또 하나.
리뷰를 믿고 코드를 대충 던지지 말자.
왜인지 마음이 급하면 깊게 생각해서 좋은 코드를 구현할 수 있음에도 ‘던져’버린다.
이건 서로간의 신뢰를 잃어버리게 한다.
어느 요리사가 대충 만든 음식을 먹고, 역시나 맛이 없었다면 그 가게에 다신 안가는 것처럼.
마지막으로 요구사항을 똑바로 보자. 몇번이고 보자.
당연하게도 요구사항은 개발의 시작점이다.
어떻게 개발해갈지 도입을 설계하는 과정.
어마무시하게 중요한 것임에도 주니어 레벨에서는 당장 기능이 동작하는 코드에 매몰되는 경향이 있다.
제대로 읽어서 정확히 질문하고 숙고한 끝에 개발을 시작하는 편이 모두를 위한 (협업) 것이다.
효율의 관점!
전체 유저 중에서 특정 유저는 제외하고 나머지 유저들에게 알림을 보내야 하는 로직을 구현한다고 해보자.
fun send(targetUser: List<String>) {
for (user in targetUser) {
if (he got reported) {
except from targetUser..
}
}
}
아마 이런식으로 작성하는 게 일반적일 것이다. (슈도 코드다)
신고를 당한 사람을 제외해야 하는데, 만약 유저테이블에 신고 당한 사람을 특정 플래그로 체크하고 있었다면
그 플래그를 통해 미리 필터링해서 조회해올 수 있었을 것이다.
그럼 그게 더 빠를까? (데이터베이스 레벨과 어플리케이션 레벨에서 필터링의 속도 차이)
fun send(userIdList: List<Long>) {
if (i in userIdList.indices) {
if (i % 2 == 0) {
userRepository.findBy(...)
}
}
...
}
또는 이런식으로 특정 조건(짝수 아이디) 일 때, 매번 필요한 정보를 조회해오는 코드가 있다고 하자.
무엇을 위한 기능이었는지 다시 살펴보고, 어떻게든 I/O 를 한번으로 줄일 수 있는 방법이 있지 않을까?
등등..
어떤 방식이 더 효율적일지 생각하면서 코드를 읽고 쓰는 것이 좋다.
어릴 적, 동작만 하면 끝이었던 코드가 아니라 더 빠른 코드를 고민하는 것이다.
이를 위해서 최소한의 자료구조와 알고리즘을 익혀두면 좋다.
그리고 코드 또한 글과 같아서, 일필휘지 하는 것은 쉽지 않다.
배포까지 나간 코드여도 조금씩 시간을 내서 다시 보면, 고칠 점이 보이곤 한다.
개인적으로 효율에서 만큼은 극단적이어도 된다고 생각한다. 😛
안전의 관점!
개발을 많이 해보지 않았다는 것은 서비스를 많이 겪어보지 않았다는 의미이기도 하다.
때문에 보안의 중요성을 놓치는 순간이 많다.
주민번호가 개인정보라는 사실은 당연히 알겠지만, 예상치 못한 값이 법에 위반되는 정보일 수 있다.
물론 기획을 비롯한 타 직군 등 동료들과의 소통에서 중요하게 고지되어 흔한 일은 아닐 테지만.
아무튼 암호화는 알자.
AES256, AES512
SECRET, IV
단방향, 양방향
...
많은 암복호화 모듈이 있지만 내용은 모르는 경우가 있다.
내 경우, 핀테크 업에 종사하면서 암복호화는 필수 그 이상이었다.
하지만 B2B로 대응하는 상대 금융사의 개발자 또는 PM, PO가 암복호화를 전혀 모르는 경우도 있었다.
직접 암복호화 모듈을 만들 필요는 없으니 왜 쓰는지와 어떻게 쓰는지, 그리고 내부적으로 대략 어떻게 동작하는 지 정도는 파악하고 있으면 더 나은 소통을 할 수 있다.
그리고 테스트코드. 🪧
이건 특히 최근에 경험한 사례다.
테스트 코드가 개발 생산성을 저하시킨다는 말을 들어보았을텐데, 틀린 말은 아닌 것 같다.
개발 생산성을 저하시키면서 동시에 서비스 안전성을 높인다.
어쩔 수 없는 trade-off 관계라고 본다.
given -> when -> then
arrange -> act -> assert
어느 방식으로 나열하던지 본질은 같다.
저 흐름을 통해 요구사항을 확실히 이해할 수 있다.
단순히 안전성 뿐만 아니라 개발자가 서비스의 본질을 이해할 수 있게 되는 것은 더 큰 의미라고 생각한다.
(개발자 중에는 서비스를 알기 귀찮아하는 놀라운 경우도 있다.)
조금 극단적으로 비교하면 이런 느낌인데
건물을 짓는데, 무거운 것을 들고 위로 올라가야해서 힘들고, 그래서 속도가 느려지니까, 안전모나 옷가지 등을 좀 버리고 빠르게 짓자.
서비스가 있기 전에, 사업이기 때문에 이익을 추구해서 빠른 개발이 필요한 순간도 있다는 걸 충분히 공감한다.
그치만 최근에는 테스트코드가 없다면 일종의 ‘부실공사’ 하는 느낌을 지우기 힘들다. 😢