나도 클린코드 짜고 싶다!

최근 회사에서 프로젝트를 하게 되었는데, Unity Inc에서 개발한 소스코드를 유지보수, 검수하는 일을 맡게 되었다. 내가 맡은 부분은 유니티 에디터로 작성된 소스코드와 프론트(Angular) 인수였는데, 기능 단위로 깔끔하께 짜여있는 코드를 보고 많이 배우게 되었다. 게다가 기능에 맞는 클래스, 함수, 변수 명은 굳이 주석이 필요할까? 싶을 정도로 보기 편했고 Unity Inc 개발자들의 실력에 그저 감탄했다.

Unity Inc 프로젝트가 끝난 후, 곧바로 AI API를 활용한 노코드 실습도구를 개발하여 배포했는데 다행히 기한에는 맞췄고 기능도 문제없었지만, 많은 아쉬움이 있었다. 최근에는 Azure 기초 자격증을 획득한 이후로 웹서비스 개발자용 Azure 자격증을 따볼까 하던 중, 오히려 클린코드를 공부해서 프론트엔드 기술력을 보강하는게 낫다고 생각하여 시작하게 되었다.


공부방법

  • Udemy 온라인강의 - 클린코드 자바스크립트(Poco Jang)의 강의를 참고하였다.

1. 변수 다루기

1) var 를 지양하자

  • var 는 재선언 OK, 재할당 OK. 그래서, 추적과 관리에 어려움이 있다.
var name = "이름1"
var name = "이름2"
console.log(name); // 이름2
  • let 은 재선언 NO, 재할당 OK. 실무에서 임시변수를 만들 때, let 을 자주 사용했다. 아래에서 다루겠지만, let 보다는 const를 사용하는 편이 좋다.
let name = "이름1"
// let name = "이름2"
// console.log(name); // 재선언시 에러
name = "이름2"
console.log(name); // 이름 2
  • const 는 재선언 NO, 재할당 NO. const 는 선언 시, 초기화가 필수다. 사용할 데이터를 분명히 할 수 있어 실수를 줄일 수 있다.
// const는 재선언 NO 재할당 NO
const name; // 초기화 하지 않으면 에러
const name = "이름1";
name = "이름2";
console.log(name); // 재할당시 에러

  • var 는 함수 스코프, let 과 const 는 블록 스코프이다. 논리적으로 구분하기에 블록 스포크가 이해하기 쉽고 더 안전하게 코드를 작성할 수 있다.
// var는 함수스코프이기 때문에 블록 영역에서 재할당했음에도 전역에도 재할당되어 오염됨
var global = '전역';
{
    var global = '지역';
    console.log(global) // 지역
}
console.log(global) // 지역, ※ 논리적으로 '전역'이 나왔어야 함
// let, const 블록스코프이기 때문에 스코프 사이에서 안전함
let global = '전역';
{
    let global = '지역';
    console.log(global); // 지역
}
console.log(global); // 전역, ※ 논리적으로 결과가 나옴
  • let 보다는 const 를 사용하는 편이 안전면에서나 불필요한 코드 작성면에서 좋다. 특히나 const를 쓰면 객체를 더 안전하게 다룰 수 있다.
// 객체 전체를 재할당하면 에러가 발생
const person = {
    name: '이동규',
    age: 35,
}
person = {
    name: '이동규',
    age: 35,
}
console.log(person) // 에러


// 객체의 원소를 지정하여 재할당할 수 있음
const person = {
    name: '이동규',
    age: 35,
}
person.name = "홍길동";
person.age = "99";
console.log(person); // { name: '홍길동', age: '99' }
  • const 로 빈 배열을 초기화하면, push 로 원소를 추가할 수 있다. 단, 전체 재할당을 하면 에러가 발생한다.
// 배열도 초기화 후, push가 가능함
const number = [];

// number = [1]; // 에러
number.push(1); // push 가능
console.log(number)

2) 전역 공간 사용을 최소화하자

  • 전역 공간을 사용해서는 안되는 이유
    • 이유 1: Nodejs의 global은 ‘global’, 브라우저의 global은 ‘window’ 이다. 만약, 의도하든 의도치않든 전역 공간에서 변수(var globalVar, var setTimeout)등을 사용하면 해당 변수들이 이미 전역에서 제공하는 예약어들과 충돌하기 때문에 예상치 못한 동작을 할수가 있다. 즉, 전역공간을 더럽혀서는 안된다.
    • 이유 2: 어디서나 접근할 수 있다는게 문제.
    • 이유 3: 스코프를 분리해도 global API나 변수를 사용하면, 공유되어버리는 예상치 못한 문제가 발생할 수 있다.
  • 더럽히지 않으려면?
    • 방법 1: 전역변수 X
    • 방법 2: 지역변수 O
    • 방법 3: window, global을 조작 X
    • 방법 4: const, let O
    • 방법 5: IIFE(즉시실행함수), Module, Closure 스코프 나누기

3) 임시변수는 사용하지 말자

  • 임시변수 사용을 줄이면, 생명주기와 사이드이펙트를 줄일 수 있다.

  • 임시변수를 제거해야하는 이유

    • 이유 1: 명령형으로 가득한 로직
    • 이유 2: 디버깅하기 힘듬
    • 이유 3: 추가적인 코드를 작성하기 쉬움 (let.. temp.. 등등)
  • 해결책

    • 방법 1: 함수 나누기 → 기능 단위로 나눌 수록 좋음
    • 방법 2: 바로 반환하기
    • 방법 3: 고차함수 (map, filter, reduce 등)
    • 방법 4: 선언형으로 작성하기
// 나쁜 예 1
const getElements = () => {
    const res={};
    res.title = document.querySelector('.title'),
    res.text = document.querySelector('.text'),
    res.value = document.querySelector('.value'),
    return(res);
}

// 좋은 예 1 - 객체로 바로 리턴하는 예
const getElements = () => {
    return (
        {
            title: document.querySelector('.title'),
            text: document.querySelector('.text'),
            value: document.querySelector('.value'),
        }
    );
}
// 나쁜 예 2
const getDateTime = (targetDate) => {
    let month = targetDate.getMonth();
    let day = targetDate.getDate();
    let hour = targetDate.getHourse();

    month = month > 10 ? month : '0' + month;
    day = day > 10 ? day : '0' + day;
    hour = hour > 10 ? hour : '0' + hour;

    return {
        month, day, hour
    };
}

// 좋은 예 2 - 재할당이 필요없고 스타일링이 좋은 예
const getDateTime = (targetDate) => {
    let month = targetDate.getMonth();
    let day = targetDate.getDate();
    let hour = targetDate.getHourse();

    return {
        month : month > 10 ? month : '0' + month,
        day : day > 10 ? day : '0' + day,
        hour : hour > 10 ? hour : '0' + hour,
    };
}

4) 호이스팅이 있어도, 선언부와 호출부는 상하단으로 구분해 작성하자

  • 호이스팅이란? 런타임 시, 선언이 최상단으로 끌어올려지는 것. 그래서 이러한 문제 때문에 코드를 작성할 때 예측하지 못한 실행결과로 실수가 발생할 수 있다.
  • 해결책
    • 방법 1: var를 사용하지 않음
    • 방법 2: 함수 표현식(나는 화살표 함수를 주로 사용함)을 사용해보자
var sum;
console.log(sum()); // 11, JS이기에 가능한. 호이스팅의 예

function sum() {
    return 1 + 2;
}
function sum() {
    return 1 + 2 + 3;
}
function sum() {
    return 1 + 2 + 3 + 5;
}

// 함수 표현식
const sum = function sum() {
    return 1 + 2;
}
console.log(sum())

// 화살표 함수
const sum = () => 1 + 2
console.log(sum());

2. 타입 다루기

1) primitive VS reference

  • 자바스크립트는 동적인 타입을 가지는 언어로서 타입검사가 어렵다.
function myfunction() { }

typeof "문자열" // string
typeof true // boolean
typeof undefined // undefined
typeof 123 // number
typeof myfunction // function
typeof null // object ← ※ API로 데이터 검증할 때 많이 애먹은 경험이 있음
  • 해결책 1
    • typeof 대신 instanceof 를 사용하는방법이 있는데, 아래는 class 의 instance 를 감지하는 용도로 주로 사용하는 instanceof 의 예시이다. (class 는 reference 타입이다.)
// instanceof 는 원래 class 의 인스턴스를 검사하는 용도
function Person(name, age) {
    this.name = name;
    this.age = age;
}

const p = {
    name: 'test',
    age: 99
}

const person = new Person('홍길동', 99);

console.log(p instanceof Person); // false
console.log(person instanceof Person); // true


// instanceof 로 타입을 검사할 수 있음
const arr = [];
const func = function () { };
const date = new Date();

// console.log(typeof (arr)) // object ← reference 객체 타입이라 typeof로 정확히 검사못함
arr instanceof Array; // true
func instanceof Function; // true
date instanceof Function; // true

// 객체 타입이기 때문에 true를 반환
console.log(arr instanceof Object); // true
console.log(func instanceof Object); // true
console.log(date instanceof Object); // true
  • 해결책 2
    • Object.prototype.toString.call() 더 다양한 타입(object array)을 검사할 수도 있다. 하지만 무적은 아니니 주의하자.
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call([{}])) //[object Array]
console.log(Object.prototype.toString.call(new String())) // object String

3. 경계 다루기

1) min - max

  • 무언가의 경계를 다룰 때, 아래와 같이 예를 들어 MIN_NUM, MAX의 값이 포함인지 아닌지를 다른사람이 읽기 어려울 수 있다.
const getRandomNumber
    = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

const MIN_NUM = 1;
const MAX_NUM = 10;
console.log(getRandomNumber(MIN_NUM, MAX_NUM))
  • 그런데, 아래와 같이 변수만 잘 작성해주어도 이해하기에도 다른사람이 읽기에도 좋다.
// 포함되어서는 안되는 경우
const MIN_NUM_LIMIT = 1;
const MAX_NUM_LIMIT = 10;

// 포함되어야 하는 경우
const MIN_IN_NUM = 1;
const MAX_IN_NUM = 10;

2) begin - end

  • 예약앱과 같이 시작일과 끝일이 있는 경우, 유용한 변수명(beginDate, endDate)의 예시이다. 참고하자.
const reservationDate = (beginDate, endDate) => {
    // ... some code
}
reservationDate('YYYY-MM-DD', 'YYYY-MM-DD'); // 인수도 확인

3) first - last

  • 시작은 알지만 끝은 알기 어려운 경우(예약자 명단 등), 유용한 변수명(first, last)의 예시이다.
const users = ['홍길동', '일동규', '이동규', '삼동규'];

const getUsers = (first, last) => {
    // ... some code
}

getUsers('홍길동', '삼동규')

4) prefix - suffix(접두사 - 접미사)

  • 접두사와 접미사를 네이밍할 때, 지정하면 코드의 일관성을 보장할 수 있다.
  • 내용이 많아 리액트 스타일 가이드 링크를 참조 (https://dev.to/abrahamlawson/react-style-guide-24pp)

5) 매개변수의 순서가 경계

  • 호출하는 함수의 네이밍과 인자의 순서의 연관성을 고려하여 함수를 만드는 습관을 들이자.

  • 함수 선언 시, 좋은 습관 3가지

    • 습관 1. 매개변수를 2개가 넘지 않도록 만든다.

    • 습관 2. arguments, rest parameter
    • 습관 3. 매개변수를 객체에 담아서 넘긴다.
// parameter 맵핑에 제한이 있음 
const sum = (num1, num2, num3) => {
    return num1 + num2 + num3
}
console.log(sum(1, 2, 3))

// parameter를 객체로 넘기면, parameter의 정의된 순서와 상관없이 맵핑할 수 있음
const sum = ({ num1, num2, num3 }) => {
    return num1 + num2 + num3
}
console.log(sum({ num1: 1, num2: 2, num3: 3 }))

다음에 다루게 될 것

  • 분기

댓글남기기