일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- uiscrollview
- Protocol
- Xcode
- Observable
- SWIFT
- 클린 코드
- combine
- UITextView
- Clean Code
- clean architecture
- uitableview
- swift documentation
- RxCocoa
- tableView
- 애니메이션
- collectionview
- rxswift
- swiftUI
- UICollectionView
- ios
- 스위프트
- Human interface guide
- map
- 리펙토링
- 리펙터링
- 리팩토링
- ribs
- MVVM
- HIG
- Refactoring
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] Clean Code(클린 코드) - 12. 냄새와 휴리스틱 (2) (일반) 본문
[iOS - swift] Clean Code(클린 코드) - 12. 냄새와 휴리스틱 (2) (일반)
jake-kim 2021. 11. 28. 23:35* 냄새와 휴리스틱: 파틴 파울러의 "Refactoring"에서 "코드 냄새"를 표현하여 주의해야하는 코드 사항들에 대해 나열한 것에 더하여 저자 로버트 C.마틴의 생각을 더한 것
일반
- 이름과 기능이 일치하는 함수
- 이름만으로 분명하지 않은 함수를 사용하면 매번 문서를 뒤적여야 하므로 처음부터 함수 이름과 기능을 확인할 것
WRONG
- 5초인지, 5주인지, 5일인지 알 수 없는 상태이므로 더욱 애매한 함수
let newDate = date.add(5)
RIGHT
- 함수의 이름이 명확한 경우
let newDate1 = date.addDaysTo(5)
- 역할 분담에 대한 의존성을 제대로 할 것
ex) 임직원들의 report를 만드는데, page가 지정된 크기 pageSize만큼 커지면 페이지를 초기화 시키는 함수
WRONG
- pageSize는 HourlyReporter가 알필요 없이, page에 관한 처리를 하는 HourlyReportFormatter가 알아야 자연스럽기 때문에, 역할분담에 대한 의존성이 잘 정의되지 않은 코드
- 만약 HourlyReportFormatter 클래스가 페이지 크기 55를 제대로 처리하지 못하면 오류가 생기는 코드
class HourlyReporter {
private let formatter: HourlyReportFormatter
private let pageSize = 55
func generateReport(employees: [HourlyEmployee]) {
employees.forEach {
addLineItemToPage($0)
if page.count == pageSize {
printAndClearItemList()
}
}
if page.count > 0 {
printAndClearItemList()
}
}
private func printAndClearItemList() {
formatter.format(page)
page.clear()
}
}
RIGHT
- HourlyReportFormatter의 메소드에 getMaxPageSize() 함수를 추가하여 역할 분담을 제대로 수행하는 방법
class HourlyReporter {
private let formatter: HourlyReportFormatter
private var pageSize { return formatter.getMaxPageSize() }
...
}
- 매직 넘버는 가독성을 위하여 명명된 상수로 교체할것
ex) 텍스트필드의 입력값이 10보다 큰 경우는 blue화면으로 변하고, 작아지면 시스템 background 색상으로 변경하는 코드
WRONG
- 해당 코드를 봤을 때 10이라는 숫자를 보고 예측해야하는 상황이므로 안좋은 코드
func setBackgroundColor() {
if myTextField.text.count > 10 {
view.backgroundColor = .blue
} else {
view.backgroundColor = .systemBackground
}
}
RIGHT
- 매직 넘버를 명명된 상수로 사용함으로서 더욱 가독성 높은 코드
let inputThresholdCount = 10
func setBackgroundColor() {
if myTextField.text.count > inputThresholdCount {
view.backgroundColor = .blue
} else {
view.backgroundColor = .systemBackground
}
}
- 분기문에 들어가는 조건식은 나열하지 말고 캡슐화 할 것
WRONG
- 조건식이 여러개이므로 읽을때 가독성이 떨어지는 코드
if (timer.hasExpired() && !timer.isRecurrent()) { ... }
RIGHT
- 조건식이 캡슐화 되어 있어서 의도가 분명히 밝혀져, 가독성이 좋은 코드
if shouldBeDeleted(timer) { ... }
- '!'와 같은 부정 조건은 한번에 이해하기 힘드기 때문에 가능하면 긍정 조건을 사용
WRONG
- 부정조건인 경우 한번에 이해하기 힘든 형태
if !buffer.shouldNotCompact() { ... }
RIGHT
- 긍정조건은 한번에 이해하기 쉬운 형태
if buffer.shouldCompact() { ... }
- 함수는 한 가지만 수행할 것 (SRP)
- 한 가지만 수행하는 좀 더 작은 함수 여럿으로 나눌 것
WRONG
- 하나의 함수에서 세 가지 임무를 수행하는 형태
func pay() {
employees.forEach {
if $0.isPayday() {
let pay = $0.calculatePay()
$0.deliverPay(pay)
}
}
}
RIGHT
- 여러가지 일을 하던 하나의 함수를 한가지의 일만 하는 함수 여러가지로 나눈 형태
func pay() {
employees.forEach {
payIfNecessary($0)
}
}
private func payIfNecessary(_ employee: Employee) {
if employee.isPayday() {
calculateAndDeliverPay(employee)
}
}
private func calculateAndDeliverPay(_ employe: Employee) {
let pay = employee.calculatePay()
employee.deliverPay(pay)
}
- 중복되는 코드들은 변수로 만들어서 사용할 것
WRONG
- 코드 여기저기에 +1에 관한 코드가 중복으로 사용되는 코드
if level + 1 < tags.length {
let parts = Parse(body, tags, level + 1, offset + endTag)
body = nil
}
RIGHT
- 여기저기서 사용되던 코드를 변수로 만들어서 깔끔하게 표현한 코드
let nextLevel = level + 1
if nextLevel < tags.length {
let parts = Parse(body, tags, nextLevel, offset + endTag)
body = nil
}
- 함수는 추상화 수준이 동일하거나 한 단계만 내려가도록 할 것
- 하나의 함수에서 추상화 개념 차이가 많이 벌어질 수록 처음보는 사람이 이해하기 어려운 코드가 되는점을 주의
ex) 페이지를 가로질러 수평자를 만드는 HTML 태그 생성하는 코드
WRONG
- 추상화 수준이 두 개가 섞여있는 코드
- 수평선에 크기의 개념이 나타난 것 (html을 모르는 사람이 보면은 이해하지 못하는 코드)
- HR 테그 자체의 문법 (html을 모르는 사람이 보면은 이해하지 못하는 코드)
func render() {
let html = "<hr"
if extraDashes > 0 {
hr.addAttribute("size", hrSize(extraDa))
}
}
RIGHT
- 추상화 수준을 제거한 코드
- 수평선에 크기의 개념이 나타난 것 -> size는 변수 이름은 목적을 반영하게 변경해으며, 변수 목적은 추가된 대시 개수를 저장, 대시(-) 개수가 연달아 4개가 나오는 경우 대시를 감지해 HR 태그로 변환하는 로직
- HR 태그 자체의 문법 -> HtmlTag라는 모델을 만들어서 HTML 문법은 HtmlTag 모듈이 알아서 처리하는 형태
func render() -> String {
let hr = HtmlTag("hr")
if extraDashes > 0 {
hr.addAttribute("size", hrSize(extraDashes))
}
return hr.html()
}
private func hrSize(_ hright: Int) -> String {
let hrSize = height + 1
return String(hrSize)
}
- 추이적 탐색을 피할 것
- A가 B를 사용하고 B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다는 의미
- ex) a.getB().getC()라는 형태를 사용한다면 설계와 아키텍처를 바꿔 B와 C사이에 Q를 넣기가 쉽지 않음
-> 만약 사이에 넣을려고 하는 경우, a.getB().getC()를 모두 찾아 a.getB().getQ().getC()로 바꾸어야 함 - 내가 사용하는 모듈이 내게 필요한 서비스를 대상으로 모두 제공하도록 설계하여, 객체 그래프를 따라 시스템을 탐색할 필요가 없도록 할 것
* 참고: Clean Code (로버트 C. 마틴)