클린 코드 저자 엉클 밥은 "클린코드는 가독성이다. 하나의 스토리를 말해준다" 라고 했으며 애자일 개발자의 원칙 저자는 "너의 의도를 reader에게 명확하게 표현하는 것이다. 가독성이 없는 코드는 영리하지 않다"라고 말했다.
비공식적으로 분 당 얼마나 WTF을 외치냐로 측정한다고 말한다. wtf/min이 적으면 행복한 개발자 팀이 있고, wtf/min이 많으면 그다지 만족스럽지 않은 개발자 팀이 있다. 여기서 중요한 점은 WTF은 "What a terrible feature"의 줄임말이다 ^^ 오해하지 말기
하지만 어떻게 클린 코드를 적을까? 여기 적용 시킬 몇 개의 규칙이 있다.
우리가 패키지, 클래스, 함수, 변수들을 만들 때 코드를 작성한다. 그리고 그들은 이름들을 갖고 있고 우리는 이 컴포넌트의 의도를 표현하는 이름을 골라야 한다. 그게 단지 변수 혹은 클래스 이름일지라도.
우리가 가독성 있는 코드를 짜려고 찾아보면 3개의 질문을 하라고 한다.
그리고 2개의 타입, 클린코드와 언클린 코드로 나누어서 살펴보자.
1) split a file path and get the latest file and its directory
"unclean"
data class GetFile(val d: String, val n: String)
val pattern = Regex("(.+)/([^/]*)")
fun files(ph: String): PathParts {
val match = pattern.matchEntire(ph) ?: return PathParts("". ph)
return PathParts(match.groupValues[1], match.groupValues[2])
}
"clean"
data class PathParts(val directory: String, val fileName: String)
val pattern = Regex("(.+)/([^/]*)")
fun splitPath(path: String) =
PathParts(
path.substringBeforeLast("/", ""),
path.substringAfterLast("/"))
2) "if-null" checks를 피하고 evils(?:)와 throw를 같이 사용할 때
"unclean"
class Book(val title: String?, val publishYear: Int?)
fun displayBookDetails(book: Book) {
val title = book.title
if (title == null)
throw IllegalArgumentException("Title required")
val publishYear = book.publishYear
if (publishYear == null) return
println("$title: $publishYear")
}
"clean"
class Book(val title: String?, val publishYear: Int?)
fun displayBookDetails(book: Book) {
val title = book.title ?:
throw IllegalArgumentException("Title required")
val publishYear = book.publishYear ?: return
println("$title: $publishYear")
}
3) Warning: 어규먼트 네이밍을 직접 사용하고 it을 자주 쓰는 걸 피해라
"unclean"
users.filter{ it.job == Job.Developer }
.map{ it.birthDate.dayOfMonth }
.filter{ it <= 10 }
.min()
"clean"
users.filter{ user -> user.job == Job.Developer }
.map{ developer -> developer.birthDate.dayOfMonth }
.filter { birthDay -> birthDay <= 10 }
.min()
더군다나 우린 아래와 같은 이유들로 코틀린을 사용할수록 불변성을 더 선호한다고 말할 수 있다.
코틀린에서 함수를 쓸 때 규칙은 무엇일까?
왜 긴 함수들은 괜찮지 않다고 하는 걸까?
왜냐면 긴 함수들은
하기 때문이다.
추가적으로 코틀린으로 함수 작성할 때 추가되는 규칙들이 있다 :
*
*
통칭 Comman-Query Separation (CQS)으로 명령형 컴퓨터 프로그래밍의 원리이다. 모든 메서드는 액션을 실행하는 명령어 또는 발신자에게 데이터를 반환하는 쿼리 중 하나여야 하지만 둘 다 해서는 안 된다.
쿼리: 결과를 반환하고 시스템의 관찰 가능한 상태를 변경하지 않는다(사이드 이펙에서 자유롭다).
명령어: 시스템 상태를 변경하지만 값을 반환하지 않는다.
1) when절 사용할 때
"unclean"
fun parseProduct(response: Response?): Product? {
if (response == null) {
throw ClientException("Response is null")
}
val code: Int = response.code()
if (code == 200 || code == 201) {
return mapToDTO(response.body())
}
if (code >= 400 && code <= 499) {
throw ClientException("Invalid request")
}
if (code >= 500 && code <= 599) {
throw ClientException("Server error")
}
throw ClientException("Error $code")
}
"clean"
fun parseProduct(response: Response?) = when (response?.code()){
null -> throw ClientException("Response is null")
200, 201 -> mapToDTO(response.body())
in 400..499 -> throw ClientException("Invalid request")
in 500..599 -> throw ClientException("Server error")
else -> throw ClientException("Error ${response.code()}")
}
2) 추상 레벨 구성을 유지하고 중첩 코드를 피해라
"unclean"
for (user in users) {
if(user.subscriptions != null) {
if (user.subscriptions.size > 0) {
var isYoungerThan30 = user.isYoungerThan30()
if (isYoungerThan30) {
countUsers++
}
}
}
}
"clean"
var countUsersYoungerThan30WithSubscriptions = 0
for (user in users) {
if (user.isYoungerThan30WithSubscriptions) {
countUsersYoungerThan30WithSubscriptions++;
}
}
클래스는 small(소규모)보다 더 작아야 한다. 오직 하나의 책임과 변경될 단 하나의 이유를 가져야 한다.(SRP) 클래스는 몇 개의 다른 클래스와 협력해서 원하는 시스템 동작을 수행한다.
클래스 파일은 신문 기사와 같다 :
응집력은 클래스 혹은 모듈의 요소가 기능적으로 관련되어 있는 정도를 나타내는 척도이다.
클래스들이 낮은 응집력을 갖고 있을 때 분리시켜라
결합은 모듈 간의 상호의존성 정도를 나타내는 척도이다.
isolation은 각 시스템의 요소를 보다 쉽게 이해할 수 있게 만든다.
베스트는 높은 응집도와 낮은 결합도를 갖는 것이다.
이 베스트 시나리오를 완성하려면 아래 원칙을 유의해서 클래스를 작성하면 된다.
Don't Repeat Yourself
코드를 복사/붙일 때마다 적용 가능
개발 비용을 절감
Keep It Simple and Stupid
모든 것을 실행하는 방법을 구현하고 싶을 때, 간단한 것이 실패가 적다.
단순은 익숙한 것과 다르다: for 루프는 익숙하지만 단순할 필요는 없다.
You Ain’t Gonna Need It
아직 필요하지 않은 코드를 작성하지 말자.
구현 비용을 평가하자: 지금 당장 실행하는 것은 비용이 많이 들기 때문에 정말 필요할 때 코드 작성을 하자.
SRP - 클래스 또는 모듈에 변경 이유가 1개만 있는 것
OCP - 클래스, 모듈, 기능 등의 소프트웨어 엔티티는 확장에 대해 개방되어 있지만 변경에는 닫혀 있는 것
LSP - 프로그램 내의 오브젝트는 해당 프로그램의 동작을 변경하지 않고 서브타입의 인스턴스로 대체할 수 있는 것
ISP - 인터페이스를 구현하는 클래스는 사용하지 않는 메서드를 강제로 구현해서는 안 된다
DIP - 상위 레벨의 모듈은 하위 레벨의 모듈에 의존하지 말고 둘 다 추상화에 의존해야 하는 것. 추상화는 디테일에 의존해서는 안 된다. 상세한 것은 추상화에 의존해야 한다.