클린코드 정리 (1)

profile image 스이연 2025. 2. 9. 03:40

예전부터 사두었던 클린코드 서적을 이번 스터디를 통해 읽게 되었다!
비록 현재는 다 읽어보진 못했지만 지금까지 읽은 내용을 1차 정리 한 후 추후 내용을 다시 정리해볼 예정이다 😊

 

01. 깨끗한 코드

비야네 스트롭스트룹
우아하고 효율적인 코드
의존성을 최대한 줄여야 유지보수가 잘된다.

 

그래디 부치
깨끗한 코드는 단순하고 직접적이다.
설계자의 의도를 숨기지 않고 추상화와 단순한 제어문으로 가득하다.

큰 데이브 토마스
작성자가 아닌 사람도 읽기 쉽고 고치기 쉽다.
단위 테스트 케이스와 인수 테스트 케이스가 존재한다.

마이클 페더스
누군가 주의 깊게 짰다는 느낌을 준다.
세세한 사항까지 꼼꼼하게 신경쓴 코드

론 제프리스
모든 테스트를 통과한다.
중복이 없다.
시스템 내 모든 설계 아이디어를 표현한다.
클래스, 메서드, 함수 등을 최대한 줄인다.

보이 스카우트 규칙

시간이 지나도 언제나 깨끗하게 코드가 유지되어야 한다.

변수 이름 하나를 개선하고 조금 긴 함수 하나를 분할하고 약간의 중복을 제거하고, 복잡한 if문 하나를 정리해보자.

02. 의미 있는 이름

의도를 분명히 밝혀라

변수나 함수, 클래스 이름을 지을 때 이것의 존재 이유, 수행 기능, 사용 방법에 대한 이해가 되도록 지어야한다.

잘못된 코드

public List<int[]> getItem(){
	List<int[]> list1 = new ArrayList<int[]>();
	for(int[] x : theList){
		if(x[0] == 4){
			list1.add(x);
		}
	}
	return list1;
}

위의 코드를 보았을 때는 코드가 어떤 일을 하는지 짐작하기 어렵다.

✔️ 필요 정보
      theList에 들어간 값
      theList에서 0번째 값이 중요한 이유
      값 4의 의미
      반환된 list1를 어떻게 사용하는가

 

올바른 코드

public List<int[]> getFlaggedCells(){
	List<int[]> flaggedCells = new ArrayList<int[]>();
	for(int[] cell : gameBoard){
		if(cell[STATUS_VALUE] == FLAGGED){
			flaggedCells.add(cell);
		}
	}
	return flaggedCells;
}

그릇된 정보를 피하라

그릇된 단서는 코드의 의미를 흐린다.

널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해서도 안된다.

여러 계정을 그룹으로 묶을 때 실제 List가 아니라면 accountList라고 명명해서는 안된다.

accountGroup, bunchOfAccounts 라고 명명하는 것이 올바르다.

서로 흡사한 이름을 사용하는 것에 주의해야한다.

의미 있게 구분하라

컴파일러나 인터프리터만 통과하려는 생각으로 코드를 구현했다가는 문제를 일으킬 수 있다.

동일한 범위 안에서는 다른 두 개념에 같은 이름을 사용하지 못한다.

연속적인 숫자를 변수에 붙여 사용하지 않는 것이 좋다. (a1, a2, a3, …)

발음하기 쉬운 이름을 사용하라

발음하기 어려운 단어는 두뇌를 활용하지 못하며 토론하기도 어렵다.

검색하기 쉬운 이름을 사용하라

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 눈에 쉽게 띄지 않는다.

간단한 메서드에서 로컬 변수만 문자 하나를 사용한다.

인코딩을 피하라

유형이나 범위 정보까지 인코딩에 넣으면 그만큼 이름을 해독하기 어려워진다.

클래스 이름

명사나 명사구가 적합하다.

좋은 예) Customer, WikiPage, Account,..

메서드 이름

동사나 동사구가 적합하다.

좋은 예) postPayment, deletePage, save, …

접근자, 변경자, 조건자는 javabean 표준에 따라 get, set, is를 붙인다.

기발한 이름은 피하라

재미난 이름보다 명료한 이름을 선택하는 것이 좋다.

의도를 분명하고 솔직하게 표현해라

한 개념에 한 단어를 사용하라

추상적인 개념 하나에 단어 하나를 선택해서 이를 고수한다.

일관성 있는 어휘를 사용하는 것이 좋다.

말장난을 하지 마라

한 단어를 두가지 목적으로 사용하는 것은 안좋다.

03. 함수

작게 만들어라

함수의 길이는 짧을 수록 좋다.

if / else문 while 문 등에 들어가는 블록은 한 줄이어야하며 대개 거기서 함수를 호출한다.

함수에서 들여쓰기 수즌은 1단이나 2단을 넘어서는 안된다.

한 가지만 해라

함수는 한 가지를 해야 한다. 그 한가지를 잘 해야 한다. 그 한 가지만을 해야한다.

public static String renderPageWithSetupAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
	if(isTestPage(pageData))
		includeSetupAndTeardownPages(pageData, isSuite);
	return pageData.getHtml();
}

 

위 함수는 한가지만 처리한다.

 

페이지가 테스트 페이지인지 확인 한 후 테스트 페이지라면 설정 페이지와 해제 페이지를 넣는다.

테스트 페이지든 아니든 HTML로 렌더링 한다.

세가지 기능을 한다고 주장할 수 있지만 이 코드에서 의미를 더 축소하기는 어렵다.

if문을 다른 함수로 따로 만들 수 있지만 똑같은 내용을 다르게 표현 할 뿐 추상화 수준을 바뀌지 않는다.

 

한가지 기능을 하는 함수인지 판별하는 또 다른 방법은 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면

그 함수는 여러 작업을 하는 셈이다.

함수 당 추상화 수준은 하나로

한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다. 특정 표현이 근본 개념인지 세부 사항인지 구별하기 어렵기 때문이다.

getHtml() 은 추상화 수준인 아주 높고, String pagePathName = PathParser.render(pagepath); 

추상화 수준이 중간이고, .append("\n") 와 같은 코드는 추상화 수준이 아주 낮다.

 

코드는 위에서 아래로 이야기 처럼 읽혀야 좋다.

한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. 즉 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 단계씩 낮아진다.

Switch 문

switch 문은 작게 만들기 어렵기 때문에 한가지 작업만 하는 switch 문도 만들기 어렵다.

안좋은 예시

public Money calculatePay (Employee e) throws InvalidEmployeeType {
	switch (e.Type) {
		case COMMISSTIONED:
			return calulateCommisionedPay(e);
		case HOURLY:
			return calculateHourlyPay(e);
		case SALARIED:
			return calculateSalariedPay(e);
		default:
			throw new InvalidEmployeeType(e.type);
	}
}

 

❗️ 문제점
      ■ 함수가 길다.
      ■ 한가지 작업만 수행하지 않는다.
      ■ SRP 를 위반한다.OCP를 위반한다.

 

이를 해결하기 위해서는 switch문을 추상 팩토리에 숨기는 것이다.

팩토리는 switch 문을 사용해 적절한 Employee 파색 클래스의 인스턴스를 생성한다.

좋은 예시

public abstract class Employee {
	public abstract boolean isPayday();
	public abstract Money calculatePay();
	public abstract void deliverPay(Money pay);
}

==============================================

public interface EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}

=================================================

public class EmployeeFactoryImpl implements EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
		switch (r.type) {
			case COMMISSTIONED:
				return calulateCommisionedPay(e);
			case HOURLY:
				return calculateHourlyPay(e);
			case SALARIED:
				return calculateSalariedPay(e);
			default:
				throw new InvalidEmployeeType(e.type);
		}
	}
}

서술적인 이름을 사용하라

함수가 하는 일을 좀 더 잘 표현하는 이름일수록 좋은 이름이다.

isTestable , includesetupAndTeardownPages 등과 같은 이름이 서술적인 이름이다.

또한 이름을 붙일 때는 일관성이 있어야한다. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.

함수 인수

이상적인 인수 개수는 0개(무항) 이며 다음은 1개, 2개다. 3개부터는 피하는 것이 좋다.

인수는 개념을 어렵게 만든다.

많이 쓰는 단항 형식

함수에 인수 1개를 던지는 흔한 이유

  1. 인수에 질문을 던지는 경우 예) boolean fileExists("MyFile")
  2. 인수를 뭔가로 변환해 결과를 반환하는 경우

플래그 인수

플래그 인수는 함수가 한꺼번에 여러 가지를 처리하기 때문에 좋지 않다.

이항 함수

인수가 2개인 함수는 1개인 함수보다 이해하기 어렵다.

이항 함수가 적절한 경우도 있지만 (예> Point p = new Point(0, 0) ) 단항 함수보다 이해하기 어렵다는 위험이 있기 때문에 가능하면 단항 함수로 바꾸도록 해야한다.

동사와 키워드

함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수다.

단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다. 예) writeField(name)

부수 효과를 일으키지 마라

부수 효과는 함수에서 한 가지를 하겠다고 약속하고선 다른 것도 한다.

부수효과는 시간적인 결합과 순서 종속성을 초래한다.

명령과 조회를 분리하라

함수는 뭔가를 수행하고나 뭔가에 답하거나 둘 중 하나만 해야한다.

public boolean set(String attribute, String value); 는 나중에 함수를 호출 했을 때 의미가 모호해진다.

if (attributeExists("username")){
	setAttribute("username", "unclebob");
}

위 처럼 명령과 조회를 분리해야 한다.

오류 코드보다 예외를 사용하라

명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.

오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.

반복하지 마라

반복되는 코드는 코드 길이가 늘어날 뿐만 아니라 알고리즘이 변하면 여러 곳을 손봐야 하기 때문에 문제가 된다.

객체 지향 프로그래밍은 코드를 부모 클래스로 몰아 중복을 없앤다.

04. 주석

주석은 나쁜 코드를 보완하지 못한다

표현력이 풍부하고 깔끔하며 주석이 거의 없는 코드가 복잡하고 어수선하며 주석이 많이 달린 코드보다 훨씬 좋다.

코드로 의도를 표현하라

주석으로 달려는 설명을 함수로 만들어 표현해도 충분하다.

주석 예시)

// 직원에게 복지 혜택을 받을 자격이 있는지 검사한다.
if((employee.flags & HOURLY_FLAG) && (employee.age > 65))

주석 없는 예시)

if(employee.isEligibleForFullBenefits())

좋은 주석

법적인 주석

회사가 정립한 구현 표준에 맞춰 법적인 이유로 특정 주석을 넣으라고 명시한다.

각 소스 파일 첫머리에 주석으로 들어가는 저작권 정보와 소유권 정보는 필요하고도 타당하다.

정보를 제공하는 주석

// 테스트 중인 Responder 인스턴스를 반환한다.
protected abstract Responder responderInstance();

이와 같은 주석이 유용하다 할지라도 가능하다면 함수 이름에 정보를 담는 편이 더 좋다.

// kk:mm:ss EEE, MMM dd, yyyy 형식이다.
Pattern timeMatcher = Pattern.compile("\\d*:\\d*\\d \\w*, \\w* \\d*, \\d*");

위와 같은 주석은 코드에서 사용한 정규 표현식이 시각과 날짜를 뜻한다고 설명한다.

이왕이면 시각과 날짜를 변환하는 클래스를 만들어 코드를 옮겨주면 더 깔끔하다.

의도를 설명하는 주석

때때로 주석은 구현을 이해하게 도와주는 선을 넘어 결정에 깔린 의도까지 설명하기도 한다.

의미를 명료하게 밝히는 주석

일반적으로는 반환값 자체를 명확하게 만들면 더 좋겠지만 인수나 반환값이 표준 라이브러리나 변경하지 못하는 코드에 속한다면 의미를 명료하게 밝히는 주석이 유용하다.

결과를 경고하는 주석

다른 프로그래머에게 결과를 경고할 목적으로 주석을 사용한다.

요즘에는 @Ignore 속성을 이용해 테스트 케이스를 꺼버린다.

public static SimpleDateFormat makeStandardHttpFormat() {
	// SimpleDateFormat은 스레드에 안전하지 못하다.
	// 따라서 각 인스턴스를 독립적으로 생성해야한다.
	SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
	df.setTimeZone(TimeZone.getTimeZone("GMT"));
	return df;
}

TODO 주석

앞으로 할 일을 //TODO 주석으로 남겨두면 편하다.

// TODO-MdM 현재 필요하지 않다.
// 체크 아웃 모델을 도입하면 함수가 필요 없다.
protected VersionInfo makeVersion() throws Exception {
	return null;
}

TODO 주석은 프로그래머가 필요하다 여기지만 당장 구현하기 여러운 업무를 기술한다.

  • 더이상 필요 없는 기능을 삭제하라는 알림
  • 누군가에게 문제를 봐달라는 요청
  • 더 좋은 이름을 떠올려달라는 부탁
  • 앞으로 발생할 이벤트에 맞춰 코드를 고치라는 주의

중요성을 강조하는 주석

String listItemContent = match.group(3).trim();
// 여기서 trim은 정말 중요하다. trim 함수는 문자열에서 시작 공백을 제거한다.
// 문자열에 시작 공백이 있으면 다른 문자열로 인식되기 때문이다.
new listItemContent(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));

나쁜 주석

주절거리는 주석

특별한 이유없이 의무감으로 혹은 프로세스에서 하라고 하니까 마지못해 다는 주절거리는 주석은 전적으로 시간낭비다.

이해가 안되어 다른 모듈까지 뒤져야하는 주석을 독자와 제대로 소통하지 못하는 주석이다.

같은 이야기를 중복하는 주석

헤더에 달린 주석이 같은 코드 내용을 그대로 중복한다면 코드보다 주석을 읽는 시간이 더 오래걸릴 수 있다.

// this.closed가 true일 때 반환되는 유틸리티 메서드다.
// 타임 아웃에 도달하면 예외를 던진다.

public synchronized void waitForClose(final long timeoutMillis) throw Exception {
	if(!closed){
		wait(timeoutMillis);
		if(!closed)
		 throw new Exception("MockResponseSender could not be closed");
	}
}

오해할 여지가 있는 주석

위에 있는 주석 예시는 중복이 많으면서도 오해할 여지가 있다.

this.closed 가 true 로 변하는 순간에 메서드는 반환되지 않는다. this.closed 가 true여야 메서드는 반환된다.

이런 식으로 잘못된 오해를 살 수 있기 때문에 오해할 여지가 있는 주석을 작성해서는 안된다.

의무적으로 다는 주석

모든 함수에 Javadocs를 달거나 모든 변수에 주석을 달아야한다는 규칙은 잘못됐다.

오히려 코드를 헷갈리게 만들며 거짓말할 가능성을 높이며 잘못된 정보를 제공할 여지만 만든다.

이력을 기록하는 주석

모듈을 편집할 때마다 모듈 첫머리에 주석을 추가했지만 혼란을 가중할 수 있기 때문에 완전히 제거하는 편이 좋다.

있으나 마나 한 주석

너무 당연한 사실을 언급하며 새로운 정보를 제공하지 못하는 주석이다.

함수나 변수로 표현할 수 있다면 주석을 달지 마라

주석 존재 예시

// 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가?
if(smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))

주석 제외 예시

ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if(moduleDependees.contains(ourSubSystem))

위와 같이 주석이 필요하지 않도록 코드를 개선하는 편이 좋다.

닫는 괄호에 다는 주석

중첩이 심하고 장황한 함수라면 의미가 있을수도 있지만 작고 캡슐화된 함수에는 잡음일 뿐이다.

닫는 괄호에 주석을 달아야겠다는 생각이 든다면 대신에 함수를 줄이려 시도해보는 것이 좋다.

주석으로 처리한 코드

주석으로 처리된 코드는 다른 사람들이 지우기를 주저한다.

소스 코드 관리 시스템이 우리를 대신해 코드를 기억해주기 때문에 주석으로 처리할 필요 없이 코드를 삭제할 필요가 없다.

05. 형식 맞추기

형식을 맞추는 목적

코드의 형식은 매우 중요하다.

오늘 구현한 기능은 다음 버전에서 바뀔 수 있기 때문에 오늘 구현한 코드의 가독성은 앞으로 바뀔 코드에 영향을 미친다.

적절한 행 길이를 유지하라

자바의 파일 크기는 클래스 클래스 크기와 밀접하다.

FitNesse 프로젝트의 평균 파일 크기는 보통 65줄이고 JUnit, FitNesses, Time and Money 는 상대적으로 파일 크기가 200줄 미만으로 작고, Tomcat과 Ant는 절반 이상이 200줄을 넘어서고 수천줄이 넘어가는 파일도 있다.

신문 기사처럼 작성하라

소스 파일을 신문 기사와 비슷하게 작성한다.

이름은 이름만 보고도 올바른 모듈을 살펴보고 있는지 아닌지를 판단할 정도로 간단하면서 설명이 가능하게 적는다.

소스파일 첫 부분은 고차원 개념과 알고리즘을 설명하고 아래로 내려갈수록 세세하게 묘사한다.

개념은 빈 행으로 분리하라

일련의 행 묶음은 완결된 생각 하나를 표현하기 때문에 생각 사이에 행을 분리하자.

패키지 선언부, import문, 각 함수 사이에 빈 행이 들어가며 이로 인해 가독성이 높아진다.

세로 밀집도

세로 밀집도는 연관성을 의미한다.

서로 밀접한 코드 행은 세로로 가까이 놓아야한다.

수직 거리

타당한 근거가 없다면 서로 밀접한 개념은 한 파일에 속해야한다. → protected 변수를 피해야 하는 이유.

같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현한다.

연관성: 하나의 개념을 이해하는데 다른 개념이 중요한 정도.

변수는 사용하는 위치에 최대한 가까이 선언한다.

지역변수는 각 함수 맨 처음에 선언한다.

인스턴스 변수는 클래스 맨 처음에 선언한다.

종속함수와 같이 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다.

가능하다면 호출하는 함수를 호출되는 함수보다 앞에 배치한다.

친화도가 높은 코드들을 가깝게 배치한다.

친화도가 높은 요인들 중에 한 함수가 다른 함수를 호출해 생기는 직접적인 요인, 변수와 그 변수를 사용하는 함수, 비슷한 동작을 수행하는 일군의 함수가 친화도가 높다.

세로 순서

호출 되는 함수를 호출 하는 함수보다 나중에 배치하게 되면 소스 코드 모둘이 고차원에서 저차원으로 자연스럽게 내려간다.

가로 형식 맞추기

짧은 행이 바람직하다.

가로 공백과 밀집도

가로로는 공백을 사용해 밀집한 개념과 느슨한 개념을 표현한다.

private void measureLine(String line) {
	lineCount++;
	int lineSize = line.length();
	totalChars += lineSize();
	lineWidthHistogram.addLine(lineSize, lineCount);
	recordWidestLine(lineSize);
}

할당 연산자를 사용할 때는 앞뒤에 공백을 주었다.

할당문은 왼쪽요소와 오른쪽 요소가 분명히 나뉘기 때문에 공백을 넣어서 구분을 확실히 해준다.

반면, 함수 이름과 이어지는 괄호 사이에는 공백을 넣지 않는다.

이유는 함수와 인수는 서로 밀집하기 때문에 공백을 넣으면 한 개념이 아니라 별개로 보인다.

가로 정렬

선언문과 할당문을 별도로 정렬하지 않는다.

정렬하지 않으면 오히려 중대한 결함을 찾기 쉽다.

들여쓰기

범위로 이루어진 계층을 표현하기 위해 코드를 들여쓴다.

클래스 정의처럼 파일 수준인 문장은 들여쓰지 않고 클래스 내 메서드는 클래스보다 한 수준 들여쓴다.

06. 객체와 자료구조

자료 추상화

구체적인 Point 클래스

public class Point {
	public double x;
	public double y;
}

추상적인 Point 클래스

public interface Point {
	double getX();
	double getY();
	void setCartsian(double x, double y);
	double getR();
	double getTheta();
	void setPolar(double r, double theta);
}

추상적인 클래스는 점이 직교좌표계를 사용하는지 극좌표계를 사용하는지 알 길이 없다. 하지만 인터페이스는 자료 구조를 명백하게 표현한다.

구체적인 클래스는 확실히 직교좌표계를 사용한다. 또한 개별적으로 좌표값을 읽고 설정하게 강제한다.

변수 사이에 함수 계층을 넣는다고 구현이 저절로 감춰지지는 않으며 구현을 감추려면 추상화가 필요하다.

추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.

자료 / 객체 비대칭

객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개하고 자료구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.

객체 지향 코드에서 어려운 변경은 절차적인 코드에서 쉬우며 절차적인 코드에서 어려운 변경은 객체 지향 코드에서 쉽다.

디미터 법칙

객체는 조회 함수로 내부 구조를 공개하면 안된다.

기차 충돌

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

위와 같은 코드를 기차 충돌이라 부르며 이는 여러 객차가 한 줄로 이어진 기차처럼 보이기 때문이다.

위와 같은 코드는 조잡해 보일 수 있으므로 아래와 같이 고치는 것이 좋다.

Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

위 예제가 디미터 법칙을 위반하는지 여부는 ctxt, Options, ScratchDir이 객체인지 아니면 자료구조인지에 달렸다.

객체라면 내부 구조를 숨겨야하므로 확실히 디미터 법칙을 위반하지만 자료구조라면 내부 구조를 노출시켜야하므로 디미터 법칙이 적용되지 않는다.

잡종 구조

때때로 절반은 객체 절반은 자료구조인 잡종 구조가 나온다.

잡종 구조는 중요한 기능을 수행하는 함수도 있고 공개 변수나 공개 조회/설정 함수도 있다.

구조체 감추기

만약 ctxt, options, scratchDir이 진짜 객체라면 내부 구조를 감춰야 하기때문에 줄줄이 엮으면 안된다.

ctxt가 객체라면 뭔가를 하라고 말해야지 속으로 드러내라고 말하면 안된다.

자료 전달 객체

자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다.

이런 자료 구조체를 자료 전달 객체(DTO)라고 한다.

DTO는 데이터베이스에 저장된 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체이다.

활성 레코드

활성 레코드는 DTO의 특수한 형태다.

공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료 구조이지만 대개 save나 find와 같은 탐색 함수도 제공한다.

💡 느낀점
     책을 읽으면서 지금까지 내가 해왔던 프로젝트들을 어떻게 구현해 왔었는지 다시 한번 생각해보면서 지켜지고 있는 것들과
     지켜지지 않는 것들에 대해서 되돌아 보게 되는 계기가 되었다.
     처음 책을 읽을 때는 이해가 가지 않는 내용이 생각보다 많아 머릿속에 다 들어오지는 못했지만 아직 다 못 읽은 내용들을 다 읽어             본 후 다시 2회독을 해보는게 내 목표다 ☺️!