- 회사에서 진행한 Clean Code 스터디에서 발표한 내용을 글로 정리해서 기록.
- 공부한 내용은 여기, 첨부된 코드 예시도 여기서 가져옴 : https://github.com/ryanmcdermott/clean-code-javascript#table-of-contents
- 내 파트는 Concurrency ~ Formatting(일부). 나머지 부분도 스스로 복습하고 여건이 되는 대로 정리해볼 예정.
Concurrency
들어가기 전에 Concurrency란?
- 동시성(병행성). 하나의 프로그램이 하나 이상의 일을 한 번에 수행할 수 있음을 의미.
- Parallelism(병렬성)과 비슷해보이나 다른 개념.
- Parallelism은 동시에 여러 작업을 한꺼번에 처리하는 것을 의미한다면, Concurrency는 여러 작업을 한꺼번에 처리하는 것처럼 ‘보이는 것’을 의미함.
- 동시성은 여러 작업을 번갈아가며 처리함으로써 사용자로 하여금 다수의 작업이 동시에 처리되는 것처럼 보이는 것.
(참고 자료 : https://www.baeldung.com/cs/concurrency-vs-parallelism)- JS는 싱글 스레드 언어지만, Event loop를 통해 동시성을 지원함. 이는 JS의 비동기처리 개념과도 연결됨.
- 요 섹션의 소제목이 Concurrency 인건 동시성에서 비롯된 JS의 비동기처리(콜백, Promise, Async/Await)를 다루기 때문.
Use Promises, not callbacks
- 비동기처리 시, 콜백을 사용하면 다음의 단점 존재.
- 콜백의 중첩으로 인한 콜백 지옥(Callback Hell)의 형성.
- 각 콜백에서 발생한 에러는 그 콜백에서 해결해야 해서, 에러 핸들링의 시점에서도 복잡함이 증가.
// Bad Example
import { get } from "request";
import { writeFile } from "fs";
get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin",
(requestErr, response, body) => {
if (requestErr) {
console.error(requestErr);
} else {
writeFile("article.html", body, writeErr => {
if (writeErr) {
console.error(writeErr);
} else {
console.log("File written");
}
});
}
}
);
→ requestErr와 writeErr를 처리하기 위해 각 콜백 내에서 if문을 통해 에러를 잡아내고 있음 ⇒ 에러 처리 중복
→ get함수에 전달되는 첫번째 콜백 내에서 writeFile의 두번째 콜백이 중첩되고 있음. ⇒ 복잡성 증가
- 콜백 대신 ES2015/ES6부터 내장 global 타입이 된 Promise를 사용하는 것을 권장.
- 콜백 지옥으로 인한 복잡한 코드를 훨씬 간단하게 만들 수 있음.
// Good Example
import { get } from "request-promise";
import { writeFile } from "fs-extra";
get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(body => {
return writeFile("article.html", body);
})
.then(() => {
console.log("File written");
})
.catch(err => {
console.error(err);
});
→ get과 writeFile에서 사용되던 콜백이 then문 chaining으로 중첩없이 깔끔히 정리.
→ 전체 과정에서 발생하는 에러는 마지막 catch 에서 한 번에 처리하는 것이 가능.
Async/Await are even cleaner than Promises
- Promise를 사용하면 콜백에 비해 더 깨끗한 코드를 만들 수 있음.
- 그러나 여전히, then문의 chaining으로 인한 중복 코드가 발생.
// Bad Example
// Actually same with good example in previous part...
import { get } from "request-promise";
import { writeFile } from "fs-extra";
get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
.then(body => {
return writeFile("article.html", body);
})
.then(() => {
console.log("File written");
})
.catch(err => {
console.error(err);
});
- ES2017/ES8에서 추가된 Async/Await를 사용함으로써, then chaining으로 인한 중복을 없앨 수 있음.
// Good Example
import { get } from "request-promise";
import { writeFile } from "fs-extra";
async function getCleanCodeArticle() {
try {
const body = await get(
"https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
);
await writeFile("article.html", body);
console.log("File written");
} catch (err) {
console.error(err);
}
}
getCleanCodeArticle()
→ 새로운 async 함수 getCleanCodeArticle이 추가됨. async 함수이기에 내부에서 await를 사용할 수 있음.
→ await를 사용하여, 위키 페이지 데이터를 가져오는 작업이 끝날 때까지 대기.
→ 이 과정에서 발생하는 에러를 try-catch를 사용하여 처리.
Async/Await를 사용하는 것이 모든 상황에서 더 나은 퍼포먼스를 보장하지는 않는다고 함.
→ await를 사용하면 해당 작업이 끝날 때까지 강제로 멈추고 기다려야하기 때문에, await를 다수 사용할 경우 수행시간이 더 늘어날 수 있음.
→ 실행해야 하는 작업이 서로 독립적일 경우, Promise.all를 사용하여 한 번에 실행하는 것이 수행시간을 줄이는 데 기여할 수 있음.
Error Handling
- 에러가 던져졌다는 것은 다음을 의미함.
- 오류 상황에서 런타임이 성공적으로 확인되었음.
- 프로세스를 kill하고, 실행을 멈춤으로써 에러를 인지시킴.
- 스택 트레이스와 하께 콘솔로 에러에 대해 알림.
Don’t ignore caught errors
- 에러를 단순히 console.log로 처리하는 것은 좋은 해결방법이 아님.
- 에러를 잡아내기 위해 try-catch문을 쓴다는 것은 해당 부분에 에러가 발생할 가능성이 있음을 인지하고 있다는 것.
⇒ 따라서 에러를 처리할 방안 또한 마련해야 함.
// Bad Example
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
→ try-catch로 에러를 잡아내나 이를 그냥 console.log로 출력만 하고 있음.
// Good Example
try {
functionThatMightThrow();
} catch (error) {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
}
→ 위 코드에서 나타나는 에러 처리법은 3가지.
- console.log 대신 console.error를 사용하기(더 눈에 돋보이고 위험하고 시끄럽게 생김)
- 사용자에게 에러에 대해 알리기 (Toast 메시지 등)
- 에러를 서비스(아마 에러 트래킹 서비스?)에 알리기
→ 물론, 위의 3가지 모두 다 해도 됨!
Don’t ignore rejected Promises
- Promise 에서 try-catch문을 통해 찾은 에러를 무시해선 안 됨.
- 이유는 위와 동일.
// Bad Example
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
console.log(error);
});
→ 위와 마찬가지로 에러를 그냥 console.log로 출력만 함.
// Good Example
getdata()
.then(data => {
functionThatMightThrow(data);
})
.catch(error => {
// One option (more noisy than console.log):
console.error(error);
// Another option:
notifyUserOfError(error);
// Another option:
reportErrorToService(error);
// OR do all three!
});
→ 해결 방안은 지난 파트와 동일.
Formatting
- Coding convention은 주관적임. 때문에 어느 것이 더 나은지 저울질할 필요 없으며, 중요한 것은 사용하기로 정한 규칙을 일관되게 지키는 것.
Use consistent capitalization
- JS는 타입이 유연한 언어이기에, 변수 이름의 convention을 통해 해당 변수에 대해 많은 것을 파악하고 있음.
- 변수 이름 규칙 또한 여러가지가 있기 때문에, 팀에서 사용할 규칙을 정하고 이를 일관성있게 지키는 것이 중요.
// Bad Example
const DAYS_IN_WEEK = 7;
const daysInMonth = 30;
const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const Artists = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restore_database() {}
class animal {}
class Alpaca {}
→ 함수 및 상수 이름에 대해 Camel case 와 Snake case를 섞어 사용하고 있음.
→ 배열과 클래스 이름의 첫 글자로 대문자와 소문자를 섞어 사용하고 있음.
// Good Example
const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;
const SONGS = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const ARTISTS = ["ACDC", "Led Zeppelin", "The Beatles"];
function eraseDatabase() {}
function restoreDatabase() {}
class Animal {}
class Alpaca {}
→ 상수/함수/배열/클래스 이름을 일정한 규칙으로 통합.
'공부 > JS' 카테고리의 다른 글
[Javascript] 클로저 알아보기 - 1 (1) | 2024.01.05 |
---|---|
[JavaScript] 실행 컨텍스트에 대하여 (2) | 2023.12.23 |
Typescript 정리 - 2 (1) | 2023.01.16 |
Typescript 정리 - 1 (1) | 2023.01.10 |
Clean Code Study - 2 (0) | 2023.01.03 |