DRY, KISS, YAGNI
아직 주니어에 '주'도 못 뗀 개발자로써, 더 좋은 코드란 무엇인가 라는 생각을 다분히 하는 편이다.
어떤 코드가 깔끔한 코드이고, 어떤 코드가 나쁜 코드인지에 대한 감이 오지 않고, 그저 최대한 내 기준에서 Nice 하게 잘 분리하고, 재사용성이 높게 코드를 짜려 노력만 한다.
그러던 와중 유튜브 알고리즘에 드림코딩 엘리의 '코딩 잘하는 팁 세가지'가 뜨게 됐는데, 내용이 너무 좋아 포스팅 하고자 한다.
DRY(Don't Repeat Yourself)
"반복하지 마라"
DRY
→ 특정한 지식, 의도, 로직 등이 다양한 곳에서 다양한 형태로 계속 반복 되어지는 것을 피하자!
시스템내에서 특정한 지식과 로직은 한 곳에서 명확하고 신뢰할 수 있도록 존재해야한다.
동일한 로직을 반복적으로 작성해 둔다면, 로직의 변경사항이 필요한 경우 반복적으로 업데이트 해야할 뿐만 아니라, 실수해서 한 군데를 빠트린다면 유지보수가 어려워진다. 반대로 로직을 한군데에서만 작성한다면 유지보수성이 높아질 것이다.
상반된 개념으로 WET(Write Every Time / Waste Everyone's Time)이 있다.
DRY 예제코드
💩 dry1-bad.js
function greetings(user) {
return `HI ${user.firstName} ${user.lastName}`;
}
function goodbye(user){
return `See you next time ${user.firstName} ${user.lastName}`;
}
이렇게 하면 문제점이 무엇일까??
같은 로직(사용자의 이름출력)이 두번 쓰이므로, 이 부분을 따로 처리 할 수 있는 곳이 있으면 좋겠다.
이유 ::
사용자의 이름을 다르게 출력한다고 했을 때 사용자의 이름 출력 부분을 모두 수정해야 한다.
💡 dry1-good.js
function greetings(user) {
return `HI ${user.fullName()}`;
}
function goodbye(user){
return `See you next time ${user.fullName()}`;
}
class User {
...
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
이렇게 수정하면, 사용자의 fullName 을 출력 하는 곳을 한 군데에 정의를 해두면 이름에 대한 코드를 수정 할 때 한군데에서만 수정 하면 된다.
DRY 는 특정한 코드의 중복을 이야기하는 좁은 의미가 아니라, 지식, 의도, 로직 이 모든 것들이 중복되지 않도록 조금 더 광범위한 범위를 가리키고 있다.
KISS(Keep It Simple, Stupid)
"심플하고, 멍청하게 유지하자"
KISS
→ 대부분의 시스템은 복잡하게 보다는 심플하게 만들어졌을 때, 최고로 잘 동작한다. 그러므로, 시스템을 디자인할 때 불필요한 복잡성은 피해야한다.
열 줄짜리 코드를 한 줄로 바꾸기 위해서 화려한 테크닉을 이용해 가독성을 떨어트리기 보다는 심플하고 간결하게 작성하는 것이 좋다.
- 1 function 1 기능
별도의 주석 없이 함수의 이름, 매개변수, 구현 코드를 읽었을 때 한번에 이해 할 수 있도록 한 가지의 기능을 수행하는 함수를 심플하게 작성하는 것이 좋다.
- 1 class 1 책임
- UI 컴포넌트에는 별도의 비지니스 로직을 넣지 말 것
- 서비스는 단 하나의 기능을 담당하는 개별적인 심플한 서비스!
KISS 예제코드
💩 kiss1-bad.js
function getFirst(array, isEven) {
return array.find((x) => (isEven ? x % 2 === 0 : x % 2 !== 0));
}
이렇게 하면 문제점이 무엇일까??
이 함수는 배열을 받아서
isEven이 참이면, 처음으로 나오는 짝수의 값을
isEven이 거짓이면, 처음으로 나오는 홀수의 값을 return 한다.
그러기에는 함수를 바로 봤을 때 한눈에 이해 되지 않을 수 있다.
가독성 문제
그럼 이렇게 바꿔볼까??
function getFirst(array, isEven) {
if (isEven) {
return array.find((x) => x % 2 === 0);
} else {
return array.find((x) => x % 2 !== 0);
}
}
하지만, 함수에서 전달되는 인자의 값이 true
인지 false
인지에 따라 다른 동작을 하게 만드는 것은 심플 하지 않다. (한 함수 에서 두가지 일을 수행)
💡 kiss1-good.js
function getFirstOdd(array) {
return array.find((x) => x % 2 !== 0);
}
function getFirstEven(array) {
return array.find((x) => x % 2 === 0);
}
// 굳이, 분기를 둬야 한다면
function getFirstByIsEven(array, isEven) {
if (isEven) {
return getFirstEven(array);
} else {
return getFirstOdd(array);
}
}
훨씬 함수의 가독성이 좋아졌다!! 🙌
💩 kiss2-bad.js
function updateAndPrint(rawData) {
// prep data...
// more code...
db.update(rawData);
// get printer...
// moer code...
printer.print(data);
}
한 함수에 두가지 기능이 들어있다!
💡 kiss2-good.js
function update(rawData) {
// prep data...
db.update(rawData);
// more code...
return data;
}
function print(data) {
// get printer...
// more code...
printer.print(data);
}
함수를 각각 개별적인 함수를 만들어 둠으로써, 이전에는 업데이트하고 프린트를 꼭 해야 했다면, 이제는 개별적으로 사용 할 수 있어졌다. 물론 같이 쓸 수도 있다.
💩 kiss3-bad.js
class LoginView {
display() {
// display view..
}
onLoginButtonClick() {
fetch('https://server.com')
.then((data) => data.json())
.then((data) => {
if (data.token) {
localStorage.setItem('TOKEN', data.token);
// Update UI Elements
} else {
// ...
}
});
}
}
UI를 담당하는 class에서 데이터를 가져오고.. 데이터를 localStorage에 쌓고.. 로직이 너무 많다
로직들은 별도의 클래스를 만들어 관리하자
💡 kiss3-good.js
class LoginView {
constructor(userPresenter) {
this.userPresenter = userPresenter;
}
display() {
// display view..
}
onLoginButtonClick() {
this.userPresenter
.login() //
.then((result) => {
// update text UI element with result.displayMessage
// update button UI element with result.buttonText
});
}
}
class UserPresenter {
userService;
login() {
this.userService
.login() //
.then((result) => {
if (result.success) {
localStorage.setItem('TOKEN', result.token);
return {
displayMessage: result.message,
buttonText: 'Go Home',
};
} else {
return {
displayMessage: 'Unable to login',
buttonText: 'Ok',
};
}
});
}
}
userPresenter 라는 별도의 클래스를 이용해 비즈니스 로직을 처리 한 다음에 처리된 데이터를 사용자에게 보여지게끔 하게 수정했다.
프리젠터
는 백엔드와 어떻게 통신하면 되는지에 대한 로직을 담고있다.
💋 KISS → 개별적으로 분리하라 !!
YAGNI(You Ain't Gonna Need it)
"야! 너 그거 필요 없어"
❌
- 필요하지 않는 기능
- 사용하지 않는 기능
- 지나치게 미래지향적
⭕️
- 깨끗하게
- 변경이 쉽게
- 유지보수 용이
YAGNI 예제코드
💩 yagni1-bad.js
function deleteUser(id, softDelete = false) {
if (softDelete){
// don't delet from db but onlt mark as deleted.
return this._softDelete(id);
}
return db.removeById(id);
}
이렇게 하면 문제점이 무엇일까??
미래에 필요하지도 않을 기능때문에 이렇게 복잡성을 추가하는 것은 좋지 않다.
이유 ::
미래에 필요하지 않을 기능 때문에 이 기능을 넣게 된다면, 우리가 기존에 작성해 두었던 데이터베이스를 다루는 모든 곳에서 업데이트를 해줘야 한다.
💡 yagni-good.js
function deleteUser(id) {
return db.removeById(id);
}
마치며
이런 원칙들을 알고 조금만 신경써서 코드를 작성하다보면 왜 함수는 가능한 하나의 작업만 수행해야하게 좋은지, 왜 가독성이 좋은 코드가 필요한지 또한 왜 인터페이스 혹은 추상클래스가 필요한지, 그리고 왜 패턴들이 필요한지 알게 되는 것 같다.
다음에는 ealry return
같이 함수에서 헛짓 안하고 빠르게 원하는 output을 퉤! 하고 뱉어줄 수 있는 여러 방법론,
또 한 번은 그냥 내가 감각적으로 아 대충 이래서 이렇지 라고 생각되는 함수의 return
문 제대로 이해하기를 TIL에 남겨볼까 한다.
좋은 소설가가 되기 위해 좋은 문학작품을 접하듯이, 좋은 개발자가 되기 위해 맛있는 코드들을 자주 접하는 것도 필요해보인다.
[ 참고 ]
- https://www.youtube.com/watch?v=jafa3cqoAVM&t - 드림코딩 '코딩 잘하는 팁 세가지'
Author And Source
이 문제에 관하여(DRY, KISS, YAGNI), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@yunkuk/TIL-02.-DRY-KISS-YAGNI저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)