2023. 12. 23. 03:50ㆍComputer Science
함수가 이해하기 쉬우려면?
- 의도를 분명히 표현하는 함수를 어떻게 구현할 수 있을까?
- 함수에 어떤 속성을 부여해야 처음 읽는 사람이 프로그램의 내부를 직관적으로 파악할 수 있을까?[작게 만들어라]
블록과 들여쓰기
if / else / while
중첩구조가 생길만큼 함수가 커져서는 안된다. 그러므로 함수에서 들여쓰기
[ 한가지만 해라 ]
- 함수는 한 가지를 해야한다. 그 한가지를 잘해야한다.그 한가지는 무엇인가? 알기 어렵다는 점
- 페이지가 테스트페이지인지 확인한 후
- 테스트 페이지라면 설정 페이지와 해제 페이지를 넣는다
- 테스트 페이지든 아니든 페이지를 HTML 로 렌더링함
-> 지정된 함수 이름 아래 추상화 수준이 하나의 단계
단순히 다른 표현이 아니라, 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러가지 작업을 하는 셈이다.
[ 함수 당 추상화 수준(abstract Level )은 하나로 ]
함수가 확실히 한가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
추상화수준이 뭐지?
추상화 수준을 알아보기 앞서 추상화(abstract) 정의를 살펴보면,
복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것
덧붙임, 자세함을 숨길수록 추상화 수준이 높고, 덧붙임이나 자세하게 명시할수록 추상화 수준이 낮은 것
1. 추상화수준(levels of abstraction) 높은 것
e.g. 커피를 내린다.
추상화수준(levels of abstraction) 낮은 것
e.g. 커피를 내리기 위해 원두의 종류를 고르고, 원두를 갈고, 필터 종이를 말아, 드립커피식으로 커피를 내린다.
2. 추상화수준(levels of abstraction) 높은 것
e.g. 오늘은 클린코드의 챕터 3을 읽는다.
추상화수준(levels of abstraction) 낮은 것
챕터 3은 함수에 대해서 말하고 있으며, 함수는 처음 읽는 사람이 프로그램 내부를 직관적으로 파악할 수 있어야한다.
// 추상화수준(levels of abstraction) 높은 것
// MARK: 클래스 - `몸상태`를 나타내는 클래스
class BodyCondition {
}
// 추상화수준(levels of abstraction) 낮은 것
class BodyCondition {
func performActivity(_ activity: ActivityType) {
switch activity {
case .sitUp:
performSitUp()
case .squat:
performSquat()
case .longDistanceRunning:
performLongDistanceRunning()
case .yoga:
performDoingYoga()
case .dynamicRest:
performDynamicRest()
}
}
}
* 코드는 🐻야곰 코드스타터 챌린지에서 내가 직접 구현해본 클래스와 그 안에 함수 코드이다. ActivityType 은 enum(열거형) 으로 함수의 전달인자로 가지고 왔다.
몸상태를 나타내는 클래스(명사) BodyCondition
클래스 안에 함수 performActivity (활동을 수행하는 함수, 동적인 메소드, 동사) 안에 보면 자세하게 다양한 케이스를 호출할 수 있는 switch - case 로 되어있다.
[ 위에서 아래로 코드 읽기 - 내려가기 규칙 ]
🤔 개인적으로 궁금한 것- pseudocode(의사)코드
필요한 행동이나 조건을 잘 설정하여 컴퓨터가 수행해야 하는 일을 절차적으로 파악할 수 있게 도와줌
-> 수도코드를 작성하는 코드도 파악하기 쉽기 위해서가 아닐까? 알고리즘 흐름을 잘 알 수 있게 말이다.
코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.
위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
NOTE
LOGO 언어에서 키워드 TO 는 루비나 파이썬에서 def 와 똑같다. LOGO 에서 모든 함수는 키워드 TO로 시작한다.
위에서 아래로 TO 문단을 읽어내려가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기가 쉬워진다.
[ Switch 문 ]
Switch 문 특성 - N 가지를 처리
=> 각 Switch 문을 저차원 클래스에 숨기고 반복하지 않는 방법
다형성(polymorphism) 이용
48p.
새 직원 유형을 추가할 때마다 코드 변경됨
-> 변경
public class EmployeeFactoryImpl implements EmployeeFactory
다형적 객체 생성하는 코드 안에서 상속관계 숨긴 채로 다른 코드에 노출하지 않는다.
[서술적인 이름 사용]
testableHtml
-> SetupTeardownIncluder
함수가 하는 일을 좀 더 잘 표현
함수가 작고 단순할 수록 서술적인 이름을 고르기도 쉬워진다.
- 이름이 길어도 좋다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
- 길고 서술적인 이름이 길고 서술적인 주석보다 좋다.
서술적인 이름 사용 => 개발자의 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
3. 이름을 붙일 때 일관성이 있어야 함.
모듈내 함수 이름은 같은 문구, 명사, 동사 사용
e.g.includeSetupAndTeardownPages
includeSetupPages
includeSuiteSetupPages
includeSetupPage
[함수 인수]
- 함수에서 이상적인 인수개수는 무항
- 단항(1개)
- 이항(2개)
- 다항(4개 이상) -> 특별한 이유가 있어야 함.
[ 테스트 관점 ]
- 인수는 더 어려움.
- 갖가지 인수 조합으로 함수를 검증하는 테스트케이스. 작성을 상상해보라.
- 출력인수는 입력인수보다 이해하기 어려움
=> 흔히 함수에 인수로 입력을 넘기고 반환값으로 출력많이 쓰는 단항형식(52p)함수의 의도와 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수다 - 동사와 키워드
e.g.
write(name) 바로 이해한다. 이름(name)은 무엇이든 write(쓴다)
=> 더 나은 뜻 writeField(name)
이름이 name 이 필드Field 라는 사실
- e.g. 함수이름에 키워드 추가*
함수이름에 인수이름 넣는다. 예를 들어assertEquals
보다assertExpectedEqualsActual(expected, actual)
이 더 좋다.
=> 인수 순서 기억할 필요 X
public boolean set(string attribute, string value)
if (set("username", "uncleBod"))
attribute 인 속성값 찾아서 값을 value 로 설정
=>성공하면 true
, 실패하면 false
를 반환
함수를 구현한 개발자는 'set' 을 동사로 의도했다.
좋은 코드 -> 혼란을 야기하지 않는 코드
명령과 조회를 분리해 혼란을 뿌리뽑기
if (attributeExists("username")) {
setAttribute("username", "unclebob");
}
많이 쓰는 단항형식
- 플래그 인수
- 이항 함수
- 삼항 함수
인수객체
- 인수목록
- 동사와 같은 키워드
부수 효과를 일으키지 마라
명령과 조회를 분리하라
[오류코드보다 예외를 사용하라]
- try - catch 블록 뽑아내기
오류처리도 한가지 작업이다. - Error.java 의존성 자석
명령함수에서 오류코드를 반환하는 방식은 명령 / 조회 분리규칙을 미묘하게 위반하다. 자칫하면 if문에서 명령을 표현식으로 사용하기 쉬운 탓이다.
여러 단계로 중첩되는 코드를 야기if (deletePage(page) == E_OK)
=> 여러 단계로 중첩되는 코드를 야기
⭐️ 오류 코드를 반환하면, 호출자는 오류코드를 곧바로 처리해야 한다는 문제에 부딪힌다.p. 58
Try / catch 블록 뽑아내기
try {
deletedPage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
catch
오류처리도 한가지 작업이다.
함수도 1가지
=> 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅함.
Error.java 의존성 자석오류 코드를 반환 == 오류 코드를 정의
public enum Error {
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCE,
WAITING_FOR_EVENT
}
⭐️ Error 코드 없이 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다.
따라서, 재컴파일 / 재배치 없이도 새 예외 클래스를 추가할 수 있다.
? 의존성 자석
Error enum 이 변한다면 Error enum 을 사용하는 클래스 전부를 다시 컴파일하고 다시 배치해야한다.
[ 반복하지마라 ]
- 중복은 소프트웨어 악의 근원
- Codd - 관계형 데이터베이스 정규형식
- 객체지향 프로그래밍은 코드를 부모코드를 몰아 중복을 없앤다.
AOP
,COP
, 구조적 프로그래밍 -> 중복 제거 전략
[ 구조적 프로그래밍 ]
다익스트라, 구조적 프로그래밍 원칙
- 모든 함수와 함수 내 모든 블록: 입구 entry, 출구 exit 하나면 됨. 루프 안에서 break, continue 사용하면 안됨.
- goto 는 절대 금지[함수를 어떻게 짜죠? (61p.)]소프트웨어를 짜는 행위: 글짓기와 비슷
- e.g. 논문, 기사 작성 생각 기록 후 읽기 좋게 다듬는다.*결론함수: 그 언어에서 동사
클래스: 동사
[ 프로그래밍의 기술 -> 언어 설계의 기술 ]
궁극적인 목표: 시스템이라는 이야기를 풀어나가기
Swift 에서의 함수
- 함수를 정의할 때 파라미터(parameter)라고 알고 있는 함수가 입력받는 하나 이상의 타입으로 된 값을 선택적으로 정의
- 반환타입이라고 알고 있는 작업이 완료되었을때 함수가 다시 전달하는 값의 타입을 선택적으로 정의할 수 있음
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
함수는 person의 이름을 입력
상수 greeting 을 반환(return)
=> `person` 이라 불리는 `String` 값인 하나의 파라미터와 그 사람의 greeting(인사말) 포함한 `String` 반환 타입을 정의
반환
반환 화살표_ `->` (하이픈 뒤에 오른쪽 방향 꺽쇄를 붙임) 뒤에 반환 타입의 이름을 붙여 함수의 반환 타입을 나타냄
매개변수, 전달인자
- 매개변수: 외부로부터 받아들이는 전달 값의 이름을 의미
- 전달인자(argument) 혹은 인자: 함수를 실제 호출할 때 전달하는 값
func hello(name: String) -> String { return "Hello \(name)" }
let helloJenny: String = hello(name:"Jenny")
print(helloJenny)
코드예시는 야곰 스위프트 프로그래밍 3판
`hello(name:)` 함수에서 매개변수는 `name` 이고,
실제 사용시 전달 받는 값 "Jenny" 가 전달인자(argument)
참고
클린코드 서적 - Robert C. Martin
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/functions/
https://ablue-1.tistory.com/81 (추상화수준 참고)
'Computer Science' 카테고리의 다른 글
[ Clean Code ] 클린코드 시리즈 - 클래스 (0) | 2024.01.14 |
---|---|
[ Clean Code ] 클린코드 - 단위테스트(Unit Test) (0) | 2024.01.13 |
[ Clean Code ] 클린코드 시리즈 - 주석 (1) | 2023.12.23 |
[ Clean Code ] 클린코드 시리즈 (with Swift 가이드라인) (2) | 2023.12.14 |
[ 정보처리기사 ] 프로그래밍 언어 활용 - Java 관련 문법 (0) | 2022.03.20 |