쾌락코딩

클린코드_의미있는 변수

|

코드를 읽는 시간 대 코드를 짜는 시간 비율이 10 대 1을 훌쩍 넘는다. 새 코드를 짜면서 우리는 끊임없이 기존 코드를 읽는다.

비율이 이렇게 높으므로 읽기 쉬운 코드가 매우 중요하다. 비록 읽기 쉬운 코드를 짜기가 쉽지는 않더라도 말이다. 하지만 기존 코드를 읽어야 새 코드를 짜므로 읽기 쉽게 만들면 사실은 짜기도 쉬워진다.

이 논리에서 빠져나갈 방법은 없다. 주변 코드를 읽지 않으면 새 코드를 짜지 못한다. 주변 코드가 읽기 쉬우면 새 코드를 짜기도 쉽다. 주변 코드를 읽기가 어려우면 새 코드를 짜기도 어렵다. 그러므로 급하다면, 서둘러 끝내려면, 쉽게 짜려면, 읽기 쉽게 만들면 된다.

의도를 분명하게 밝혀라

변수나 함수 그리고 클래스 이름은 다음과 같은 굵직한 질문에 모두 답해야 한다. 변수(혹은 함수나 클래스)의 존재 이유는? 수행 기능은? 사용 방법은? 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.

의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워진다.

// 지뢰찾기 게임 중 일부
public List<int[]> getThem() {
  List<int[]> list1 = new ArrayList<int[]>();
  for (int[] x : theList)
    if (x[0] == 4)
      list1.add(x);
  return list1;
}

이 코드가 좋지 못한 이유는 코드의 함축성이다. 코드 자체에 코드 맥락이 명시적으로 드러나지 않았다. 더 좋은 코드로 변경하기 위해서는,

  1. theList에 무엇이 들었는가?
  2. thiList에서 0번쨰 값이 어째서 중요한가?
  3. 값 4는 무슨 의미인가?
  4. 함수가 반환하는 리스트 list1을 어떻게 사용하는가?

더 좋은 아래와 같은 코드로 변경할 수 있다.

public List<Cell> getFlaggedCells() {
  List<Cell> flaggedCells = new ArrayList<Cell>();
  for (Cell cell : gameBoard)
    if (cell.isFlagged())
      flaggedCells.add(cell);
  return flaggedCells;
}

단순히 이름만 고쳤는데도 함수가 하는 일을 이해하기 쉬워졌다. 바로 이것이 좋은 이름이 주는 위력이다.

의미 있게 구분하라

연속적인 숫자를 덧붙이 이름(a1, a2, …, aN)은 의도적인 이름과 정반대다. 이런 이름은 아무런 정보를 제공하지 못하는 이름일 뿐이다. 저자 의도가 전혀 드러나지 않는다.

// javascript
function copyChars(a1, a2) {
  for (let i in a1) {
    a2[i] = a1[i]
  }
}

함수 이름으로 source와 destination을 사용한다면 코드 읽기가 훨씬 더 쉬워진다.

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

문자 하나만 사용하는 변수 이름은 문제가 있다. 루프에서 반복 횟수를 세는 변수 i,j,k는 괜찮다.(l은 절대 안된다.) 단, 루프 범위가 아주 작고 다른 이름과 충돌하지 않을 때만 괜찮다. 루프에서 반복 횟수 변수는 전통적으로 한 글자를 사용하기 때문이다. 그 외에는 대부분 적절하지 못하다.

똑똑한 프로그래머와 전문가 프로그래머 사이에서 나타나는 차이점 하나만 들자면, 전문가 프로그래머는 명료함이 최고라는 사실을 이해한다. 전문가 프로그래머는 자신의 능력을 좋은 방향으로 사용해 남들이 이해하는 코드를 내놓는다.

클래스 이름

클래스 이름과 객체 이름은 명사나 명사구가 적합하다. Customer, WikiPage, Account, AddressParse 등이 좋은 예다. Manager, Proccessor, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.

메서드 이름

메서드 이름은 동사나 동사구가 적합하다.

생성자를 중복정의(overload)할 때는 정적 팩토리 메서드를 사용한다. 메서드는 인수를 설명하는 이름을 사용한다.

// java
Complex fulcrumPoint = Complex.FromRealNumber(23,0);
//위의 코드가 아래 코드보다 좋다
Complex fulcrumPoint = new Complex(23,0);

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

추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다. 예를 들어, 똑같은 메서드를 클래스마다 fetch, retreive, get으로 제각각 부르면 혼란스럽다.

말장난을 하지마라

add 메서드와 insert 또는 append는 맥락이 다르다. 분명히 메서드의 역할에 따라 적절하게 부여해야한다.

프로그래머는 코드를 최대한 이해하기 쉽게 짜야한다. 집중적인 탐구가 필요한 코드가 아니라 대충 훓어봐도 이애할 코드 작성이 목표다.

정리

책을 읽는 내내 좋은 코드, 읽기 좋은 코드에 대한 구체적인 개념이 들어섰다. 좋은코드, 읽기 좋은 코드는 독자가 마치 하나의 재미있는 소설을 읽는 듯이 쭈욱 읽혀나가는 코드다. 실제로 책의 많은 예제를 보니 메서드 이름, 변수 이름만 대충보아도 흐름이 보이고 맥락이 보이는 경험을 할 수 있었다. 협업하는 환경에서는 먼저 코드를 읽어야 코드를 짤 수 있으므로 코드의 시작은 코드를 읽는 것이다. 그 시작이 재미있는 소설처럼 읽힌다면 코드짜는게 더 즐거워 질 것 같다.

크로스브라우징이란?

|

크로스 브라우징이란?

세상에는 크롬, 사파리, 인터넷 익스플로어, 오페라, 파이어폭스, 웨일 등등 많은 브라우저들이 존재한다. 이렇게 많은 브라우저들의 동작 방식은 W3C라는 국제 웹 표준화 기구에서 제공하는 스펙(가이드라인)을 따라서 동작하게 된다. 그러나 표준화 기구에서 제공하지 않는 스펙에 대한 아주 디테일한 내용들은 각자의 상황에 맞게, 각자의 스타일에 맞게 구현하게 되어있다. 그래서 개발자들은 자신이 짠 한줄의 코드가 모든 브라우저에서 동일하게, 그리고 올바르게 동작할 것이라 생각하면 안된다. 예를 들어 과거 netscape라는 브라우저에서는 이벤트 등록을 아래의 메서드로 했다고 한다.

addEventListner

그러나 IE는 아래와 같다

attachEvent

이게 바로 크로스브라우징 이슈이다. (모바일 브라우저들까지 생각해야한다..)

수많은 브라우저들

어떻게 해결하나?

다행스럽게도 각 브라우저에서 돌아가는 js로 사용자가 현재 사용하고 있는 브라우저가 어떤 것인지 알수 있다. navigator객체가 대표적이다. 아래는 크롬 브라우저에서 navigator객체를 출력해본 이미지이다.

네비게이터 객체

크롬에서는 appName이 chrome이 아니라 Netscape라고 뜬다. 오류가 아니라 정상이라고 한다(어떤 이유인지는 검색해보면 나올듯!). 지금은 Netscape라는 브라우저가 없지만, 파이어폭스가 넷스케이프를 계승했기 때문에 파이어폭스에서도 Netscape라고 나온다. 아무튼 이를 활용해 브라우저를 확인하고, 그에 맞게 크로스브라우징 이슈를 해결하는 다소 노가다성 해결방법이 있다.

또다른 해결 방법은 라이브러리를 활용하는 것이다. jQeuryunderscore.js, Modernizr 등등의 라이브러리들이 제공하는 함수를 활용한다면 내부적으로 크로스브라우징 이슈를 해결해준다.

추가적으로 css의 경우도 크로스브라우징이 발생하기 때문에 고려해야한다. 아래의 사이트를 자주 방문하며 css 크로스 브라우징에 대처하자.

위와 같은 온라인 tool을 사용해서 이슈를 해결하도록 노력하자

참고자료

python3 사용시 팁

|

유용하게 쓸수 있는 상수들

공식문서

import string 

string.ascii_lowercase # 소문자 abcdefghijklmnopqrstuvwxyz
string.ascii_uppercase # 대문자 ABCDEFGHIJKLMNOPQRSTUVWXYZ
string.ascii_letters #대소문자 모두 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
string.digits # 숫자 0123456789

진법 변환

n진법으로 표기된 string을 10진법 숫자로 변환

int(x, base = 10) 함수는 진법 변환을 지원한다.

num = '3212'
base = 5
answer = int(num, base) // 432

원본을 유지한채, 정렬된 리스트 구하기

sorted함수.

list1 = [3, 2, 1]
list2 = sorted(list1)
print(list1) // [1, 2 ,3]

반면 sort함수는 원본을 변경한다.

console.log(0.1 + 0.2 == 0.3); 의 결과는?

|

Front-end-Developer-Interview-Questions에서 본 질문이다. 처음엔 == 와 === 를 비교하는 문제인가 싶었는데 아니었다.

숫자

이 문제는 컴퓨터 내부 원리에 대해 아주 살짝의 이해가 필요했다. 젼혀 겁먹을 것은 아니고 진법 변환에 대한 내용이다. 컴퓨터는 우리가 주로 사용하는 10진수를 내부적으로 2진수로 변환시켜 계산을 한다. 컴퓨터는 0과 1로 모든 것을 처리하기 때문이다. 예를 들어 10진수 3은 2진수 11, 10진수 4는 2진수 100. 이렇게 변환되는 것이다.

소수점을 처리할 경우에도 마찬가지인데, 몇몇 소수는 특이하게도 무한하게 긴 2진수가 된다고 한다. 당연히 컴퓨터 메모리는 용량이 정해져 있기 때문에 무한한 2진수는 적절하게 자를 수 밖에 없고, 이렇게 자르게 됨으로써 아주 미세한 숫자 차이(오차)가 생기게 된다.

console.log(0.1 + 0.3) // 0.4
console.log(0.1 + 0.2) // 0.30000000000000004

코드에서 보듯이 0.1 + 0.3은 우리가 기대한 것처럼 0.4 가나온다. 이는 0.1 과 0.3 모두 2진수로 변환했을 때 무한한 숫자가 아니기 때문에 그런 것이다. 반면 0.1 + 0.2는 아주 아주 미세한 차이로 우리가 기대한 0.3과 다른 값을 출력한다. 이 것은 0.2가 2진수로 변환되었을 때 무한한 숫자가 되어 내부적으로 유한한 숫자로 바꾸기 위해 끝 부분을 자름으로써 오차가 발생했기 때문이다.

True가 되게 하는 방법

toFixed() 라는 함수가 있다.

(0.1+ 0.2).Fixed(1); // "0.3"

이렇게 매개변수로 받은 숫자만큼의 소수자리까지 반올림해서 출력한다.

숫자관련 메소드 사용시 주의사항

아주 중요한 것은 아니지만 주의해야 할 사항이 있다.

1.toFixed(2) // SyntaxError: Invalid or unexpected token

위의 코드를 실행하면 “1.00”이 나올 줄 알았는데 에러가 난다. 반면

1.232.toFixed(2) // "1.23"

이 코드는 정상 출력된다. toFixed() 함수가 float형태의 숫자에서만 가능해서 그런게 아니다. js가 멍청해서 그런 것이다….(?) js는 숫자 부분에서 . 이 나오면 당연히 소숫점이라 생각해서 . 뒤에 소수가 올 것이라 생각한다. 그런데 1.toFixed(2); 같은 경우에는 .뒤에 숫자가 아니라 함수가 와버려서 js가 당황한 나머지 에러를 뿜어준 것이다. 반면

1.232.toFixed(2) // "1.23"

이 코드가 정상 출력된 이유는, 숫자에 . 이 두번오는 경우는 없으니 첫번째 . 은 소수점으로 이해한 것이고, 두 번째 오는 . 에서는 함수가 올 것을 예상한 것이다.

그럼

1.toFixed(2) // SyntaxError: Invalid or unexpected token

이 코드를 정상 수행 하고 싶다면,

1. toFixed(2) // "1.00"

이렇게 1. 뒤에 의도적으로 뛰어쓰기를 하면 된다.

참고자료

이벤트 위임(event delegation) feat.bubbling and capture

|

이벤트 버블링(Event Bubbling)

브라우저는 특정 element(div 등)에서 이벤트(click 등)가 발생했을 때, 그에 해당하는 이벤트를 자신의 부모 요소를 포함해 최상위 element인 document 요소까지 이벤트를 전파시킨다.

간단한 코드를 보자면,

// css
#one{
  height: 300px;
  width: 300px;
  background-color: green;
}
#two{
  height: 200px;
  width: 200px;
  background-color: yellow;
}
#three{
  height: 100px;
  width: 100px;
  background-color: blue;
}
<body>
  <div id="one">
  one
    <div id="two">
     two
     <div id="three">
        three
      </div>
    </div>
  </div>
</body>
var divThree = document.getElementById("three");
var divTwo = document.getElementById("two");
var divOne = document.getElementById("one");

divThree.addEventListener('click', printLog, {capture: true});
divTwo.addEventListener('click', printLog);
divOne.addEventListener('click', printLog, {capture: true});

function printLog(event) {
	console.log(event.currentTarget.id);
}

이렇게 하면 결과물은 아래 이미지와 같다. event1

여기서 가장 아래 요소인 파란색 three 영역을 클릭하면 콘솔에는 아래와 같이 출력된다.

three
two
one

three만 클릭했음에도 불구하고 two와 one이 출력되었다. 이유는 이벤트 버블링 때문인데 three가 클릭될 때 부모요소부터 body까지 click이벤트를 전파시키는 것이다. 여기서 two를 클릭한다면 결과는

two 
one

일 것이고, 만약 two 요소의 이벤트가 click이 아니라 다른 이벤트가 등록이 되어있다면 그 것은 실행되지 않는다. 이런 원리로 인해 부모 요소에서 자식 요소의 이벤트를 감시할 수 있다. 그 것을 이용하는 것이 이벤트 위임(delegation)이다.

참고 - 나는 그냥 three를 클릭하면 three만 출력되길 바란다. 즉 버블링을 없애고 싶다!

이럴 경우에는 event객체의 stopPropagation() 메서드를 사용하면 된다.

function printLog(event) {
  event.stopPropagation();
	console.log(event.currentTarget.id);
}

이벤트 위임(delegation)

버블링의 원리를 이용해서, 자식 요소가 많을 경우 자식 요소 하나하나에 이벤트를 붙이지 않고, 부모 요소에서 자식 요소의 이벤트들을 제어하는 방식이다.

<body>
  <ul id="list">
   <li id="one">
     one
   </li>
   <li id="two">
     two
   </li>
   <li id="three">
     three
   </li>
  </ul>
</body>
var list = document.getElementById("list");


list.addEventListener('click', printLog);


function printLog(event) {
 console.log(event.target.id);
}

event2

위의 경우 li 요소에 이벤트를 하나하나 등록하지 않고도 원하는 이벤트 처리를 할 수 있다. one을 클릭하면 one이 출력되고, two를 클릭하면 two가 출력된다. li 요소를 클릭했을 때, bubbling으로 인해 부모의 click이벤트가 실행되기 때문이다.

이렇게 이벤트 위임을 하면 장점이 있다.

  • 동일한 여러개의 이벤트를 한 곳에서 관리하기 때문에 관리가 수월해진다.
  • 동적으로 이벤트를 매번 추가하지 않기 때문에 메모리 사용이 효율적이다.

이벤트 캡쳐(capture)

이벤트 캡쳐는 이벤트 버블과 반대 방향으로 진행되며, 최상위 요소로부터 이벤트가 발생한 요소를 찾아가는 방식이다. 물론 찾아가는 과정에서 동일한 이벤트를 실행한다.

사용하는 방법은 {capture: true} 옵션을 주는 것이다.

var divThree = document.getElementById("three");
var divTwo = document.getElementById("two");
var divOne = document.getElementById("one");

divThree.addEventListener('click', printLog, {capture: true});
divTwo.addEventListener('click', printLog, {capture: true});
divOne.addEventListener('click', printLog, {capture: true});

function printLog(event) {
	console.log(event.currentTarget.id);
}

동일하게 three를 클릭하면 bubbling때와 달리

one
two
three

가 출력된다. 만약 코드가 아래와 같다면?

var divThree = document.getElementById("three");
var divTwo = document.getElementById("two");
var divOne = document.getElementById("one");

divThree.addEventListener('click', printLog, {capture: true});
divTwo.addEventListener('click', printLog); // 여긴 capture하지 않음
divOne.addEventListener('click', printLog, {capture: true});

function printLog(event) {
	console.log(event.currentTarget.id);
}

three를 클릭했을 때 결과는

one
three
two

이다. 즉, capture를 할 요소들만 캡쳐가 되고, 그렇지 않은 two같은 경우는 평소와 같이 버블링 되어서 three가 실행된 이후에 실행된다.