클린코드 - 02. 의미있는 이름

의도를 분명히 밝혀라

변수(혹은 함수나 클래스)

  • 존재이유?
  • 수행기능?
  • 사용방법?

이 모든 것을 답하기 위해 주석이 필요하다면 의도를 분명히 드러내지 못했다는 뜻.

 

그릇된 정보를 피하라

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

  • 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안된다
    • 실제 컨테이너가 List가 아닐 경우 List로 명명하면 그릇된 정보를 제공하므로 이렇게 명명하지 않는다
    • 실제 List여도 컨테이너 유형의 이름에 넣지 않는 것이 바람직하다
  • 유사한 개념은 유사한 표기법을 사용한다
    • 이름만 보고 정보를 추측하기 때문에 일관성이 떨어지는 표기법은 그릇된 정보다
    • 연관성이 없는 것에 대해 비슷한 단어를 사용했을 경우 연관이 있다고 생각하는 오류를 범하기 때문에 흡사한 이름을 사용하지 않는다.
  • I과 O는 숫자와 유사하기 때문에 그릇된 정보가 될 수 있다

 

의미있게 구분하라

단순 컴파일러나 인터프리터를 통과하는 네이밍은 문제를 일으킨다.

  • 연속적인 숫자를 덧붙이지 않는다
    • a1, a2, ....aN
    • 의도적인 이름과 정반대이며 아무런 정보를 제공하지 못하는 이름이다
    • 저자의 의도가 드러나지 않는다
  • 불용어를 추가한 이름은 아무런 정보도 제공하지 못한다
    • Info, Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어이다
      • 접두어를 사용하지 말라는 소리가 아니다
      • Name, NameString에서 Name은 String일 확률이 대부분 이므로 불용어다
      • customerInfo - customer, accountData - account, TheMessage - message와 구분이 안되기 때문에 차이를 알 수 있도록 한다 

 

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

발음하기 어려운 이름은 토론하기도 어렵다

  • 긴 단어를 합쳐서 줄임말로 쓸 경우 의사소통에 문제가 생길 수도 있다

 

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

문자 하나를 사용하는 이름(짧은)보다 길더라도 검색해서 찾아내기 쉬운 이름이 좋다

  • e로만 검색할 경우 무수히 많은 검색 결과가 나온다
  • 버그가 있을 경우 찾기 쉬운 이름이 짧은 이름보다 때론 좋다

 

인코딩을 피하라

  • 헝가리식 표기법
    • 에디터 등의 발전으로 변수 이름에 타입을 인코딩할 필요가 없다
    • 타입을 바꾸기가 어려워지고 읽기도 힘들다
    • 독자를 오도할 가능성도 커진다
  • 멤버 변수 접두어
    • 클래스와 함수는 접두어가 필요없을 정도로 작아야한다
  • 인터페이스 클래스와 구현 클래스

 

자신의 기억력을 자랑하지 마라

  • 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야한다면 바람직하지않다
    • 일반적인 변수 이름이 아니었기에 생기는 문제
  • 문자 하나만 사용하는 변수 이름은 문제가 있다
    • 루프에서 범위가 작고 다른 이름과 충돌하지 않을 때는 괜찮다
      • 루프에서 반복 횟수 변수는 전통적으로 한 글자를 사용하기 때문에...
    • a와 b를 사용한다고 가정했을때 c를 선택해 사용한다면 최악이다

 

클래스 이름

  • 명사나 명사구가 적합하며 동사는 사용하지 않는다.
  • Manager, Processor, Data, Info 등과 같은 단어는 피한다

 

메서드 이름

  • 동사나 동사구가 적합
  • Javabean 표준에 따라 값 앞에 get, set, is를 붙인다

 

기발한 이름은 피하라

  • 의도를 분명하고 솔직하게 표현해야한다

 

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

  • 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다
    • fetch, retrieve, get | controller, manager, driver으로 제각각 쓰면 혼란스럽다
  • 이름이 다를 경우 당연히 클래스, 타입도 다르다 생각한다

 

말장난을 하지마라

  • 한 단어를 두 가지 목적으로 사용하지 마라
    • add가 하나의 값에 더한다는 의미라면 집합에 값을 추가하는 경우에는 add보다 insert나 append라는 이름을 사용한다

 

해법 영역에서 가져온 이름을 사용하라

기술 개념에는 기술 이름이 가장 적합한 선택이다

  • 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다

 

문제 영역에서 가져온 이름을 사용하라

'프로그래머 용어'에 없다면 문제 영역에서 이름을 가져와도 괜찮다

 

의미있는 맥락을 추가하라

스스로 의미가 분명한 이름이 없지 않다

  • 접두어를 붙인다
    • firstName -> addrFirstName이라는 접두어를 통해 주소의 일부라는 사실을 분명히한다

 

불필요한 맥락을 없애라

애플리케이션 네임을 모든 클래스의 이름의 시작에 넣는 것은 바람직하지 않다

 


 

 

문헌

클린코드 - 로버트 C. 마틴

클린코드 - 01. 깨끗한 코드

원초적 난제

기한에 맞추려면 나쁜코드를 양산할 수 밖에 없다고 느낄 것이다.

하지만 코드를 최대한 깨끗하게 유지하는 습관이 기한 맞추는 유일한 방법일지 모른다.

 

깨끗한 코드라는 예술?

"깨끗한 코드가 무엇일까?"

"어떻게 작성할까?"

'깨끗한 코드'가 무엇인지 조차 모른다면 노력해봤자 소용없다.
하지만 이 말이 '깨끗한 코드'와 '나쁜 코드'를 구분할 줄 알아야만 '깨끗한 코드'를 작성할 수 있다는 의미는 아니다.
‘코드 감각’이 있다면 개선할 방안이 떠오를 것이다.

다만 '코드 감각'은 누군가는 타고날수도 있고 누군가는 경험을 통해 얻을 수도 있다. 

 

깨끗한 코드란?

비야네 스트롭스트룹

"우아하고 효율적인 코드를 좋아한다."

우아하고 효율적인 코드를 좋아한다. 효율적인 코드는 단순 속도만을 의미하지 않고 CPU의 자원또한 포함할 것인다.

함수가 너무 많은 일을 할 경우 의도가 뒤섞이고 목적이 흐려진다. 한가지를 제대로해야하며 논리가 간단해야 버그가 숨어들지 못한다. 또한 서로에 대한 의존성을 줄여야 유지보수가 쉬워진다.

많이 놓치는 오류처리, 메모리누수, 경쟁상태, 일관성 없는 명명법 등도 신경써야하며 오류는 전략에 의거해 처리한다. 

성능을 최적으로 유지해야만 원칙없는 최적화를 하지않게된다. 원칙없는 최적화는 코드를 망칠 확률이 있다.

 

그래디 부치

"깨끗한 코드는 단순하고 직접적이다. 
가독성을 강조하고 잘 쓰여진 문장처럼 읽혀야한다."

깨끗한 코드는 단순하고 직접적이다. 

가독성을 강조하여 잘 쓰여진 문장처럼 읽혀야하며 설계자의 의도를 숨기지 않고 명쾌한(사실에 기반하여) 추상화와 단순한 제어문을 사용한다.

*명쾌한: 힘차고 단호하고 사실적인

 

데이브 토마스

"테스트 없는 코드는 깨끗한 코드가 아니다."

작성자가 아닌 사람이 읽기는 물론 고치기도 쉬울만큼 가독성 있는 코드가 깨끗한 코드이다.

읽기 쉬운 것과 고치기 쉬운 것은 엄연히 다르다.

테스트 주도 개발(TDD)이 오늘날 가장 근본적인 원칙 중 하나가 된 것을 보아라, 테스트 없는 코드는 깨끗한 코드가 아니다.

- 의미 있는 이름을 붙이고 목적을 달성하는 방법을 하나만 제공하라.

- 의존성은 최소로하고 각 의존성을 명확히 정의한다.

- API는 명확하며 최소로 줄인다. 최소는 큰 코드보다 작은 코드에 가치를 둔다는 의미로 코드는 작을 수록 좋다.

- 언어마다 모든 정보를 코드만으로 표현할 수 없기에 코드는 문학적(인간이 읽기 좋게)으로 표현해야한다


마이클 페더스

"깨끗한 코드란 언제나 누군가 주의 깊게 짰다는 느낌을 주며
고치려봐도 손 댈 곳이 없다."


론 제프리스

"중복이 없으며 간단한 코드, 단순한 코드 규칙으로 시작하고 끝낸다."

초반부터 간단한 추상화를 고려하며 시스템 내 모든 설계 아이디어를 표현한다.

의미 있는 이름으로 명명하며 객체나 메서드가 여러 기능을 수행한다면 여러개로 나누고 클래스, 메서드, 함수 등을 최대한의 크기로 줄인다.

모든 테스트를 통과해야한다.

 

워드 커닝햄

"코드가 그 문제를 풀기위한 언어처럼 보인다면 아름다운 코드라 불러도된다."

코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드다.

 

우리들 생각

"깨끗한 변수 이름, 함수, 클래스"

 

 

결론

책을 읽는다고 뛰어난 프로그래머가 된다는 보장은 없다.

그저 뛰어난 프로그래머가 생각하는 방식과 그들이 사용하는 기술, 기교, 도구(경험적 교훈, 체계, 절차, 기법 등)를 소개하는 것을 보고 난 이후는 본인들이 하기 나름이다.

 


“연습해, 연습!”

 


문헌

클린코드 - 로버트 C. 마틴

콜 스택(Call stack)과 힙(Heap)

자바스크립트 엔진이 자바스크립트를 실행할 때 원시 타입 및 참조 타입을 저장하는 메모리 구조

  • 콜 스택 : 원시타입 값함수 호출의 실행 컨텍스트(Execution Context) 저장
  • : 객체, 배열, 함수와 같이 크기가 동적으로 변할 수 있는 참조타입 값 저장

 

동작 원리

이미지 및 코드 출처 : https://github.com/baeharam/Must-Know-About-Frontend/blob/master/Notes/javascript/stack-heap.md

let a = 10;
let b = 35;
let arr = [];

function func() {
  const c = a + b;
  const obj = { d: c };

  return obj;
}

let o = func();

 

1. 전역 실행 컨텍스트(GEC, Global Execution Context) 생성 후 원시값은 콜 스택에 참조값은 힙에 저장

  • 주소 값과 배열, 함수 값을 제외하고는 모두 원시 값(그리고 함수의 호출 값)이므로 콜 스택에만 저장된다.

  • 이때 함수의 호출은 콜 스택에 주소 값은 저장되나 값이 없다.

  • 주소 값과 배열, 함수의 값은 참조 값이므로 콜스택과 힙에 저장된다.

 

2. 함수 func()가 실행되면 새로운 함수 실행 컨텍스트(FEC, Function Execution Context)가 생성되며 원시 값이나 참조 값에 따라 각각 콜 스택, 힙에 동일하게 저장된다.

 

함수 컨텍스트의 원칙 4가지

  • 전역 컨텍스트 생성 후 함수 호출 시 함수 컨텍스트가 생성된다.

  • 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope, chain, this가 생성된다.

  • 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수 객체 안에서 찾고 없으면 스코프 체이닝으로 찾는다.

  • 험수 실행이 마무리되면 해당 컨텍스트는 사라진다.

    • 다만 클로저일 경우에는 제외되며 전역 컨텍스트는 페이지 종료 시 사라진다.

 

 

 

3. 함수가 종료되면 함수 실행 컨텍스트는 사라진다.

 

 

4. 전체 코드 종료 후 페이지가 종료되면 전역 실행 컨텍스트가 사라진다.

이때 전역 실행 컨텍스트 제거에 따라 스택의 값이 없기 때문에 힙에서 참조하고 있던 값이 없어지므로 가비지 컬렉터에 의해 제거된다.

자바스크립트는 객체 생성 시 자동으로 메모리 할당, 참조된 값이 없으면 자동으로 가비지 컬렉션에 의해 메모리 해제가 된다.

++ 오해하면 안되는 것은 가비지 컬렉션이 자동으로 해제해준다고 해도 메모리 관리는 해줘야한다.

 

 


출처 및 참조

콜 스택(Call stack)과 힙(Heap)

https://github.com/baeharam/Must-Know-About-Frontend/blob/master/Notes/javascript/stack-heap.md

실행 컨텍스트

https://www.zerocho.com/category/JavaScript/post/5741d96d094da4986bc950a0

 

모듈 시스템: CommonJS, AMD, UMD, ES6

여러 기능에 관한 코드가 모여있는 하나의 파일

 

 

장점

  • 의존성을 줄여주기때문에 유지보수 용이
  • 모듈만의 네임스페이스 화 
  • 필요할때마다 재사용

위와 같은 이유로 모듈의 개념이 필요했고, Javascript에서는 여러가지를 시도하였다.

 

CommonJS

  • 서버사이드 및 데스크탑 어플리케이션에서 지원하기 위해 만든 방식으로 Node.js에서 사용 가능하다.
  • require, module.exports를 사용하는 방식이다. 
  • 여기서 module.exports의 module은 예약어이며 현재 모듈에 대한 정보를 가지고 있는 객체이다.
  •  
// search.js
const getWord = () => {};

module.exports = {
    getWord
};

// index.js
const searchModule = require('./search.js');
searchModule.getWord();

module.export를 exports로도 사용이 가능하며 관계를 명확히 이해해야한다.

  • module.exports빈 객체를 참조
  • exportsmodule.exports 참조
  • requiremodule.exports를 리턴받음

 

module.exports = { searchModule }; 
// module.exports는 빈객체를 잠조하므로 -> 빈객체 = { searchModule }

exports.searchModule = searchModule; 
// exports가 module.exports를 참조하므로 -> module.exports.searchModule라는 객체 = searchModule 

exports는 항상 module.eports를 참조하기 때문에 exports를 사용하면 직접 module.exports를 수정하지 않고 객체의 멤버를 만들거나 수정이 가능하다. 잠재적인 버그를 피할수 있다는데???? 추후 자세히 알아보자.

 

 

AMD(Asynchronous Module Definition)

  • 비동기 모듈에 대한 표준안.
  • 모듈 로딩이 다 될때까지 동기로 기다릴 수 없기 때문에 비동기 모듈방식이 브라우저쪽에서 더 큰 효과를 발휘한다.
  • define(), require()를 사용하는 방식이다.
  • 모듈로더로는 RequireJS를 추천한다.

HTML

<!DOCTYPE html>
<html lang="ko">
<head>
  <title>Document</title>
</head>
<body>
  <!-- data-main="require가 로드된 후 실행될 파일" src="requireJS에서 비동기로 사용할 파일" -->
  <script data-main="index.js" src="require.js"></script>
</body>
</html>

JS

// index.js
require.config({
  // 기본 경로
  baseUrl: '/',
  // 모듈에 해당하는 경로
  paths: {
    searchModule: 'searchModule',
    loaderModule: 'loaderModule',
}});

// 첫번째 인자에 해당하는 모듈이 로드되었을 경우 a로 받아서 getWord() 함수를 호출하는 콜백함수 실행.
// 의존성 모듈을 지정해주는 것
require(['searchModule'], (searchModule) => {
	searchModule.getWord();
});

// searchModule.js
// define을 통해 정의, require에서 의존성 모듈을 설정한 것처럼.. 
// 여기서도 콜백함수가 실행되기전 모듈을 지정할 수 있다.
define(() => {
  return {
  	getWord: () => 'word'
  }
});

 

 

UMD(Universal Module Definition)

  • CommonJS, AMD를 통합하기 위한 하나의 패턴.
(function (root, factory) {

    // AMD
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);

    // CommonJS
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();

    // Browser
    } else {
        // Browser globals (root is window)
        root.returnExports = factory();

}}(typeof self !== 'undefined' ? self : this, function () {
    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

공식코드 출처 : https://github.com/umdjs/umd/blob/master/templates/returnExports.js

 

 

 

ES6(ES2015) 방식

  • import, export 방식을 사용한다.
  • 모든 브라우저가 지원하지 않기 때문에 Babel의 @babel/plugin-transform-modules-commonjs을 통해 컴파일한다.
  • export하는 방식에 따라 import해서 사용하는 방식이 달라진다.
// 1. export default
const getWord = () => {};
export default getWord;

// 1. import
import getWord from 'searchJS';

// 2. export {}
const getWord = () => {};
export { getWord };

// 2. import
import {getWord} from 'searchJS';

default export는 모듈내에서 한번만 사용가능하며 named export는 여러번 사용가능하다.

namex dexport로 내보내면 { }를 통해 불러오고 as 키워드를 통해 다른이름으로 사용하거나 *를 통해 한번에 불러오거나 등의 작업을 할 수 있다.

참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/export

 

 

 


 

모듈 시스템: CommonJS, AMD, UMD, ES6

https://github.com/baeharam/Must-Know-About-Frontend/blob/master/Notes/javascript/module.md

 

+ Recent posts