# 10년 이상 쓸 수 있는 지속 가능한 코드 작성
###### tags: `냥하` `강의`
## 지속 가능한 코드
- 단순하고 의존성이 적은 코드
- 큰 기업들은 잘 돌아가는 기존의 코드를 잘 바꾸지 않는다.
## 좋은 코드란 무엇일까?
1. 사람이 읽기 좋아아한다.
2. 간결하고 의도가 명확해야한다,
- "컴퓨터가 이해할 수 있는 코드는 바보라도 작성할 수 있다. 좋은 프로그래머는 사람이 이해할 수 있는 코드를 작성한다" _ 마틴 파울러
3. 의존성이 적은 코드
- 과도한 의존성을 경계하자
- npm 패키지에 과도한 의존성을 갖는 코드에 대한 재고
4. 하나의 함수, 메소드, 클래스는 각각 명확한 하나의 책임만 지니도록 하자.
- 접속사 없이 한 문장으로 설명할 수 없으면 다시 고민해보자
## 디버깅
- 일반적인 디버깅의 단계(어디에서 말해야 좋을까)
1. 정상 시스템의 동작, 그러니까 **기대하는 동작**을 정의한다.
2. 원인을 섣불리 추측하지 않고, 일단은 "모른다"고 생각하고 출발한다.
3. 근본적인 원인을 알 때까지 최대한 많은 데이터를 모으며 현상을 관찰한다.
4. 증상이 아닌 **원인을 수정**한다.
- 프론트 관점에서의 디버깅
1. 정확한 재현과정
- 사용자의 액션/환경, 시스템적 차이, 브라우저 확장 기능, 사용자의 인터넷 환경등 원인이 되는 주변 요소들이 다양함.
2. 기대 동작
- 왜 이것을 에러라고 인식했는가?
- 정확한 기대 동작이 있어야 유의미한 버그가 나타날 수 있음
3. 에러 메시지
- 브라우저 콘솔, 에러 메시지를 잘 읽자, 그리고 그 메시지가 내포하고있는 의미까지 알아보자.
- 오타, 변수나 함수 이름을 잘 못 쓰는 경우, 컴파일이 없는 JS에서 실행 전에는 알 수 없는 에러들
- 정적 분석: 실행해보지 않고 코드를 분석해 문제점을 찾아내는 것(ex, TS)
- 프론트는 로그 수집을 하지 않기 때문에, 에러메시지가 나타나지 않으면 원인 찾기가 힘듦
## 테스트
- 방어적으로 프로그래밍하고 극단적인 상황도 가정해보자
- 테스트 커버리지는 단순히 어떤 결과를 내기 위해 실행된 코드의 양을 측정할 뿐. 해당 코드에 관한 단언문이 없다면 테스트를 하지 않은 것으로 봐야한다는 시각도 있다.
- 엣지 케이스(=경계 케이스): 주어진 조건이 극한의 상황일 때 발생하는 문제, 일부러 테스트가 실패할 법한 값을 넣어서 테스트를 진행해보는 것
## 좋은 테스트의 조건
1. 실행 속도가 빨라야 함.
2. 내부 구현(테스트하지 않는 부분)을 변경했다고 해서 깨지면 안됨.
- 인터페이스(입출력 위주)를 중심으로 작성. 비공개 메스드도 테스트를 해야하는가라는 논쟁을 가끔 볼 수 있는데 테스트의 목적이 모든 코드를 테스트하자는 게 아니라 공용 인터페이스가 작동하도록 보장하는 데 있음을 명심.
4. 버그를 찾을 수 있어야 한다. 테스트 시나리오를 잘 설정
6. 테스트 결과에 일관성이 있어야 함. 코드는 안 변했는데 실행할 때마다 테스트 결과가 달라지면안됨
7. 의도가 명확히 드러나야 함.
## 코드 커버리지
: 어떤 라인이 실제로 실행되었는지 확인하는 것
- 보통 라인 수를 기준으로 측정하고, 70%만 넘어도 훌륭한 수준
## TDD, BDD
#### 장점
1. 테스트를 먼저 작성하기 때문에 전체 코드에서 얼마나 많은 코드가 테스트되는가를 측정하는 테스트 커버리지 비율이 자연스럽게 높아진다.
2. 테스트 되는 것만 코드로 작성하므로 코드가 방대해지지 않는다.
3. 버그때문에 발생하는 시간 낭비 줄여주고, 코드가 원하는 바를 명확히 달성하는지 쉽게 확인
#### how to test?
1. 테스트를 먼저 작성한다. 만족하는 코드가 없는 상태이므로 당연히 테스트는 실패함.
2. 테스트를 통과하는 코드를 작성한다.
3. 리팩터링: 중복이 보이거나 더 개선할 방법이 있다면 코드를 개선한다.
## 리팩터링
: 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법 _ _마틴 파울러, 리팩터링 2판,
#### "3의 법칙" _돈 로버츠
1. 처음에는 그냥 한다.
2. 비슷한 일을 두 번째로 한다면(중복이 생겨 당황스럽겠지만) 그래도 일단은 그냥 진행한다.
3. 비슷한 일을 세 번째 하면 리팩터링한다.
#### 악취가 나는 코드
1. 너무 큰 함수나 클래스
2. **이름이 명확**하지 않은 함수나 변수 이름. 지나치게 짧은 것보다는 조금 긴 게 나음.
3. **중복 코드**: 같은 일을 하는 코드가 여기저기 산재해있으면 수정
4. **전역 변수**: 가능하면 함수 내 혹은 모듈 내에 두자
5. 과도한 콜백. 조건문 중첩
6. 과도하게 긴 식별자
#### 리팩토링 방법
1. 함수 추출 혹은 옯기기: 로직을 별도의 함수나 모듈로 분리
```
function processPosts() {
const posts = getPosts();
...
posts.forEach(post => {
console.log(post);
});
...
}
// 리팩토링 후
function logPosts(posts) {
posts.forEach(post => {
console.log(post);
});
}
function processPosts() {
const posts = getPosts();
...
logPosts(posts);
...
}
```
2. 중간 변수 도입: 어떤 값인지 설명하는 중간 변수 도입.
```
const user = getUser();
if (user.authKey) {
...
}
// 리팩토링 후
const user = getUser();
const isLoggedIn = Boolean(user.authKey);
if (isLoggedIn) {
...
}
```
조금 복잡한 계산이 필요하다면 함수로 만드는 방법도 있음
```
const user = getUser();
const purchases = getPurchaseHistory(user);
// 지금까지 구매 내역이 없는 한국어 사용자에게 이벤트 배너 표시
if (user.authKey && user.locale === 'kr' && purchases.length === 0) {
showEventBanner();
}
// 리팩토링 후
function isEligibleForEvent(user) {
const purchases = getPurchaseHistory(user);
const isLoggedIn = Boolean(user.authKey);
const isKoreanUser = user.locale === 'kr';
const hasPurchaseHistory = purchases.length > 0;
return ( isLoggedIn && isKoreanUser && ! hasPurchaseHistory );
}
const user = getUser();
if (isEligibleForEvent(user)) {
showEventBanner();
}
```
3. (JS) var가 보이거든 let, const로 변환. let 보다는 const 위주로 사용. let이 보인다면 꼭 필요한 것인지 다시 한 번 고려.
4. 함수의 사이드 이펙트 최소화. 가능하면 순수 함수로 작성하자.
- 순수 함수: 똑같은 입력이 들어가면 똑같은 출력, 함수의 역할이 명확하게
5. 조건식 통합: 책에서는 '조건식 통합'이랬지만 차라리 분리하고 함수로 나누는 방법을 오히려 추천. 코드가 더 명확함. 그렇지 않다면 변수 이름으로 받아서 작성하는 방법도 있음.
- 들여쓰기와 괄호 중괄호 조합때문에 오히려 지저분하게 느껴지기도 해서...
6. 빠른 실패: fast fail. 들여쓰기 단계를 줄여줌
- 자바스크립트 기본기에서 콜백을 줄이는 패턴으로 소개한 적 있음,
7. 반복문 보다는 파이프: 이해하기 쉬워서.
```
// 예시1
const names = [];
for (const i of input) {
if (i.job === 'programmer') {
names.push(i.name);
}
}
const names = input.filter(i => i.jpb === 'programmer').map(i => i.name);
// 예시2
const items = [1, 2, 3];
for (let i = 0; i < items.length; i++) {
let item = items[i];
console.log(item);
}
[1, 2, 3].forEach( item => {
console.log(item);
} );
```
굳이 따지자면 성능면에서는 손해인데 막 수만건씩 다루는 게 아닌 이상 없다고 봐야 할 정도이므로 걱정하지 말고 쓰세요.
8. (강의 쌤의 취향) 조건, 반복문에 중괄호는 꼭 쓰는 편. 한 줄에 표현할 정도로 짧고 명확하면 생략하기도 함.
9. switch 대신 object literal
```
// switch
function 직업을말해봐(job) {
switch(job) {
case 'engineer' :
console.log('엔지니어');
break;
case 'developer':
console.log('개발자');
break;
case 'developer':
console.log('개발자');
break;
}
}
// object literal(명확, 새로운 값 추가하기에 편리)
function 직업을말해봐(job) {
const translated = {
engineer: '엔지니어',
developer: '개발자',
designer: '디자이너',
}
console.log(translated[job])
}
```
10. 배열이나 객체는 불변 객체처럼 Immutable하게 다루자. 새로운 값을 추가할 때, 변화가 추적되지 않아서 문제가 많이 생김
```
const blackPink = [ '지수', '제니', '로제', '리사' ];
blackPink.forEach((value, index) => {
blackPink[index] += '❤️';
});
console.log(blackPink);
// 리팩토링 후
// blackPinkWithLove 새로운 배열을 만들어서 저장 -> 변화 추적에 유리
const blackPink = [ '지수', '제니', '로제', '리사' ];
const blackPinkWithLove = blackPink.map((value, index) => {
blackPink[index] += '❤️';
});
console.log(blackPinkWithLove);
```
11. 문자열 합치기 보다는 ES6 템플릿 문자열(백틱 사용하여 문자열을 합치는 방법)
=> 자신뿐만아니라 팀원을 위해서라도 지속가능한 코드를 짜보자!