더 효과적인 피어 코드 리뷰를 위한 여섯가지 방법

한빛미디어에서 당신의 동료가 더 효과적으로 코드리뷰를 할 수 있게 만드는 여섯 가지 방법 라는 좋은 글을 접했습니다. 그런데 읽다보니 번역문이 이해하기 어려운 점이 많아서 원문을 다시 읽게되었고 요약하게 되었습니다. 아래 그 내용을 공유합니다.

디자이너에겐 크리틱(critique – 비평) 이 있고, 개발자에겐 코드리뷰가 있다.

좋은 비평이란 나쁜 피드백을 좋은 피드백으로 감싼 샌드위치같은 것이 아니다. 좋은 비평을 위해서는 아래 세가지가 필요하다.

  1. 합의된 검토 수단
  2. 리뷰어의 객관성
  3. 자신의 작업물을 객관적으로 바라볼 수 있는 작성자

정량적 검토

논쟁이 생기기 어려운, 대부분 쉬운 결론이 나는 검토. 한쪽은 옳고, 다른쪽은 그르다. 적절한 정량적 검토수단을 가지면 작업물을 적절한 자동화 테스트에 넣으면 되므로 리뷰어들을 자유롭게 함. 예를들면 코딩 규칙을 지켰는지 등이 정량적 검토에 속함.

정성적 검토

의견들로 가득한, 들어보면 모두 옳은, 쉽게 결론이 나지 않는 검토. 매우 어렵기 때문에 정성적 평가 수단을 만드는 것은 시도조차 하지 않는 경우가 많음. 하지만 작성자, 리뷰어 모두의 스킬을 올려줄 만하다. 정성적 평가 수단은 비 전문가인 리뷰어에게 코드에서 어떤 요소를 찾아야 하는지 알려줌.

정성적 검토에는 아래와 같은 질문들이 포함됨:

  • 이 코드가 이 프로젝트에서 사용된 적 있는 패턴을 구현했는지?
  • 이 코드가 다른 상황에서도 사용할 수 있을 정도로 충분히 추상화 되었는지?

커뮤니케이션의 어려움

평가라는 것은 결국 도덕적인 평가와 닮아있음. 좋은 리뷰어는 위에 언급된 평가 방법 내에서 다른 것을 많이 더하지 않고도 피드백을 표현할 수 있음. 이건 결국 작성자들에게 (항상 비슷한 수준으로) 중립적으로 대응할 수 있도록 함.

이론적으로는 리뷰어가 말하는 의도를 작성자는 완벽하게 알아들을 수 있음. 하지만 사람들이 모국어가 아닌 외국어로 읽고 쓰는 경우가 많기 때문에 그런일은 잘 없음. 커뮤니케이션은 어렵고, 특히나 중립적인 커뮤니케이션은 더더욱 어렵다. 사실 중립적인 의사소통은 종종 적대적 편향 – 모호한 의사소통을 적대적으로 해석하는 인간의 특성 – 때문에 망가진다.

유용한 팁들

아래 가이드 라인을 따르기 전에 평가가 적당한 유형으로 이뤄지는지 확인해야함. 일단 커뮤니티가 기능과 광범위한 세부 구현에 대해 동의하면 코드를 작성하고 그 후엔 리뷰가 진행되어야 한다

리뷰 중에는 아래 사항들을 따르는지 확인할 것:

  1. 리뷰를 작업의 영역으로 제한하기. 효율적인 리뷰는 문제에 대해 레이저로 쏘는 것처럼 한다. 추가적인 고려사항이 필요한, 범위를 넘어서는 문제는 여기에 딸린 액션 아이템(이슈트래커의 티켓 같은)으로 만들것.
  2. 리뷰를 특정하기. 특정 라인에 대해, 컴포넌트에 대해서만 언급할 것. 일반적인 문제로 만들지 말고 명확하게 표현할 것.
  3. 리뷰를 실천가능하게 할 것. 리뷰한 것을 어떻게 변경구현할지 매우 명백해야 함. 그렇지 않으면 해당 리뷰는 지울것.
  4. 빠른 시간 내로 리뷰를 전달할 것. 리뷰를 전달하는데 오래걸리면 자신의 작업물을 왜 그렇게 개발했는지에 대해 잊어버리기 쉽다. 필자는 리뷰하는 시간을 동료와 미리 정해 놓고 함께 작업함. 잘 안되는 경우도 있지만 작은 팀에서 효율적임.
  5. 해당 문제에 대해 코더가 소요할 시간을 리뷰의 일부로 고려해야 함. 세줄을 고치는데 몇시간이 소요될 수도 있다. 인내하고 이슈를 해결하는데 시간이 걸릴 수 있음을 인지해야함.
  6. 감사하라. 오픈소스 프로젝트라면 일반적으로 기여자들에게 기여의 의무는 없었다. 특히 직장에서는 정말 싫어하는 프로젝트의 일원이 되어서 일할 것을 강요받는 경우도 있다. 감사하다고 이야기하라. 이 모든 것이 달라질 것이다.

램디스크로 더 빠른 개발환경 구축하기

최근에 한 웹앱 프로젝트를 완료했다. 이 프로젝트를 진행하면서 램디스크를 오랫만에 사용해보게 됐다.

웹앱은 특히 HTML템플릿을 많이 사용했는데 그 특성상 처음 퍼블리싱된 파일이 너댓개 템플릿으로 분리되는 것이 예사였다. 파일이 많아지면서 프로젝트 내에서 파일이름을 검색해 여는 단순한 작업도 은근히 느려지는 것을 느꼈다. 그리고 파일 내부 검색에도 시간이 오래걸렸다.

처음에는 SSD 로 업그레이드를 해야하는가 고민했지만 마침 얼마전 램을 16GB로 업그레이드 하면서 평소 메모리 사용량이 꽤 많이 남게 되어서 활용하기로 했다.

램드라이브를 사용하게 되면서 파일이름을 검색하든 파일 내부를 검색하든 버벅거림이 전혀 없고 아주 만족하며 사용중이다. 파일을 이용하는 모든 작업이 매우 빨라졌다. git 이 속도가 빠른 이유는 로컬에 저장하기 때문인데 램디스크를 사용하면 브랜치 이동 등이 더 빨라진다.

스크립트 소개

아래 스크립트는 OSX 에서만 동작한다. 램디스크를 만들고, 어플리케이션을 실행하는 부분들이 특히 그렇다. 소스는 http://github.com/elegantcoder/ramdisk 에 있다.

스크립트는 Makefile 로 되어있다. 쉘스크립트를 사용할 수도 있었겠지만 arguments 로 분리하는 것을 해본적이 없어서 섹션을 쉽게 나눌 수 있는 Makefile 을 사용했다.

타겟

start
램디스크를 만들고 하드디스크의 프로젝트 파일을 램디스크로 카피해둔다.
stop
make copy 를 실행하고 램디스크를 제거한다.
sync
주기적으로 make copy 를 실행한다.
apps
작업을 시작하면서 필요한 어플리케이션들을 실행하도록 했다. 나의 경우는 터미널, Sublime Text 와 Source Tree 를 실행하도록 했다.
copy
램디스크의 파일을 하드디스크에 복사하는 작업을 한다. rsync 를 사용해보기도 했는데 잠시 생성했다 지운 파일도 복사되는 경우가 있어서 파일을 삭제하고 다시 넣도록 했다. 특히 git 브랜치 이동시에 문제가 발생하는 경우가 많다. 그리고 새로운 이름으로 복사 후 이름을 바꿔주는 편이 안전하다고 생각해 그렇게 구현했다.

변수

새로 작성시에 변수의 마지막 슬래시에 특히 주의해야 한다.

srcPath
하드디스크 내의 소스디렉토리 이다. ex) ~/Development/project
volumeName
만들어질 램 디스크의 볼륨이름이다. 띄어쓰기 금지
targetPath
램디스크가 위치할 볼륨 디렉토리이다. 수정금지.
projectPath
램디스크내 프로젝트가 위치할 디렉토리이다. 램디스크의 root 디렉토리에는 시스템 파일등이 위치하게 되므로 파일을 복사하기가 좋지 않아 볼륨의 루트에서 한단계 아래 디렉토리에 프로젝트를 위치시켰다.
size
만들어질 램디스크의 크기이다. 단위는 섹터 수이다. 섹터는 512byte 단위이므로 아래 공식을 사용하면된다. (1GB*1024byte^3)/512byte.
syncInterval
초단위로 싱크할 주기를 넣는다. 1200 이라면 20분에 한번꼴이다.
tempDir
파일 복사에 사용할 임시디렉토리이다. 현재 시간의 md5 해시 스트링을 사용하도록 해서 무작위로 생성되도록 했다. 굳이 수정할 필요는 없다.

참고

자바스크립트의 변수형을 알아내는 다른 방법

JavaScript 의 변수형을 알아내는 데는 일반적으로 typeof 를 사용한다. 하지만 toString 을 사용하면 어떤 객체의 인스턴스인지까지 한번에 알아낼 수 있다.

JavaScript 의 변수형을 알아내는 데는 일반적으로 typeof 를 사용한다.

이 방법이 가장 간단하고 일반적인 방법이다.

typeof 가 나타낼 수 있는 변수형은 아래와 같다. undefined, object, boolean, number, string, function, object

typeof 가 변수에 대한 기본적인 호기심을 해결해주긴 하지만 몇가지 모자란 점이 있다. 가장 큰 문제는 어떤 객체의 인스턴스인지 정확히 알려주지 않는다는 점이다.

typeof null 은 ‘object’ 이다.

typeof null === 'object'; // true

typeof [] 도 ‘object’ 이다.

typeof [] === 'object'; //true

따라서 어떤 객체인지 더 알아보려면 조건판단을 더 해주어야 한다. 예를들면 날짜 객체인지 알아보려면 아래처럼 구현하기도 한다.

var date = new Date();
if (typeof date === 'object' && date instanceof Date) {
    console.log 'date';
}

instanceof 는 같은 윈도우 환경에서만 작동하므로 위 방법은 프레임/아이프레임/팝업 같은 환경에서는 실패한다.

다른 방법

변수형을 알아내는 다른 방법도 있다. 바로 .toString() 을 사용하는 것이다.

Object.prototype.toString.call({}) === "[object Object]" //true

이 방법은 객체 타입까지 구체적으로 표현해주는 장점이 있다.

Object.prototype.toString.call(new Date) === "[object Date]"; //true
Object.prototype.toString.call(/s/) === "[object RegExp]"; //true
Object.prototype.toString.call(/null/) === "[object Null]"; //true
Object.prototype.toString.call(1) === "[object Number]"; //true

어떤 원리인지 자세히 알아보고 싶다면 참고링크의 Fixing the JavaScript typeof operator 항목을 참조하기 바란다.

underscore.js 에서는 아래와 같이 사용하고 있다.

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) == '[object ' + name + ']';
    };
  });

성능문제

하지만 이 방법의 단점은 성능이다. 사실 프로토타입의 객체를 call로 부르는 구현법이 typeof 라는 연산자보다 성능이 좋을리가 만무하다. 참고링크에 JSPerf 항목에서 underscore의 이전 구현/현재 구현간의 성능차이를 확인할 수 있다.

성능문제에도 불구하고 객체의 타입까지 손쉽게 알아낼 수 있으므로 효율적인 개발을 가능하게 할 것이고, 이 방식의 매우 큰 장점이다.

참고링크

웹호스팅환경에서 git push 때 자동으로 pull 하기

ssh, bare repository, post-receive hook 을 사용해 로컬저장소에서 push 하면 호스팅 서버에서 자동으로 pull 받을 수 있는 환경을 꾸며봅니다.

Bitbucket 에서 private 저장소를 만들어 버전관리를 하고 있는 프로젝트가 있다. 웹 프로젝트이고 이 프로젝트의 결과물은 Cafe24 에 월 500원짜리 웹호스팅에서 서비스된다.

처음엔 remote origin 을 Bitbucket 으로 두고, 이 저장소를 통해 로컬과 호스팅 서버간의 통신을 했다. 소스를 고치고 push 하고, 호스팅서버의 커맨드라인에서 pull 을 해야했으니 매우 불편했다. ftp 때는 저장만 하면 자동으로 ftp 로 올려주는 프로그램도 있었는데, 더 좋은 도구를 쓰는데 해야할 일은 더 많아졌다.

물론 이럴 때 쓰기 위해 hook 이 존재하는 것이다. 호스팅서버에서 커맨드라인으로 git을 지원하므로 호스팅 서버에 베어 저장소를 만들어 hook 을 걸었다.

아래는 그 과정이다.

호스팅서버에서 git bare 저장소를 만든다.

[email protected] $ git init --bare ~/myproject.git

git bare 저장소의 .git/hooks/post-receive.sample 파일을 편집해 post-receive 로 다시 저장한다. 나의 경우는 아래 처럼 했다. 아래 내용은 getting “fatal: not a git repository: ‘.’” when using post-update hook to execute ‘git pull’ on another repo 에서 참고했다.

cd ~/myproject || exit
unset GIT_DIR
git pull origin master

호스팅 서버에 서비스 디렉토리를 만들고 bare 저장소를 clone 해둔다.

[email protected] $ git clone ~/myproject.git ./myproject

이미 존재하고 있는 로컬 저장소에서 새로운 git bare 저장소를 origin 으로 설정하고, push 한다. 기존의 Bitbucket 프로젝트는 bb 라는 이름으로 다시 저장했다. (이미 origin 이 존재한 경우만 아래와 같이 한다. origin 이 없었던 경우에는 clone 으로 한다.)

[email protected] $ git remote rename origin bb
[email protected] $ git remote add origin [email protected]:~/myproject.git
[email protected] $ git push origin master

로컬저장소에서 내용을 변경해 push 해 테스트한다.

[email protected] $ git commit -am "hook test"
[email protected] $ git push origin master

처음부터 hook 을 사용할 것을 생각하지 않아 remote origin을 지우고 다시 넣고 하긴 했지만, 작업을 진행하면서 계속 머리속을 맴도는 질문이 있었다.’이런 작업을 svn 에서는 어떻게 처리했지?’

서버를 떼어다 옮기거나 리모트 스크립트를 호출하지 않으면 중앙집중식에서는 이와 같은 방법으로는 처리할 수가 없다. 이것이 바로 중앙집중저장소와 분산저장소의 차이가 된다. 여러 서버 저장소를 두고 선택적으로 push 할 수 있다는 점. 이것이 분산 버전관리의 장점이라고 생각한다.

p.s

  • cafe24 의 git 을 활용하면 월 500원짜리 private git 저장소를 만들어 사용하는 것도 가능하다.
  • 호스팅 서버에 Bitbucket 저장소도 remote 로 추가하고 hook 에서 git push bitbucket 이라는 라인도 추가해놓으면 push 한번에 bitbucket 과 호스팅 서버에 동시 저장도 가능하겠다.

jQuery 요소 선택의 성능문제, 셀렉터와 탐색 중 어떤 것이 더 빠를까?

스택오버플로를 둘러보다 아래와 같은 질문을 찾았다.

Is jQuery traversal preferred over selectors?

jQuery 에서 요소 선택의 성능문제를 묻는 질문인데,

$("#vacations").find("li").last();

$("#vacations li:last");

이 두 가지중 어떤 것이 더 빠를까? 하는 질문이다.

질문자도 그렇고, 개인적인 생각으로도 처음엔 후자가 더 빠르지 않을까했는데, 베스트 답변은 생각과는 조금 달랐다.

결론부터 말하자면 jQuery 셀렉터가 아니라 표준 CSS 셀렉터를 사용하면 빠르다. 그렇지 않으면 탐색이 더 낫다

답변이 좀 길지만, 읽어볼 만한 가치가 있어보여 아래 번역한다.

There’s no cut and dry answer to this, but with respect to the :last selector you’re using, it’s a proprietary extension to the Selectors API standard.Because of this, it isn’t valid to use with the native .querySelectorAll method. What Sizzle does is basically try to use your selector with .querySelectorAll, and if it throws an Exception due to an invalid selector, it’ll default to a purely JavaScript based DOM selection/filtering. This means including selectors like :last will cause you to not get the speed boost of DOM selection with native code.

이 문제에 대한 명백한 해답은 없지만 질문에 나와있는 :last 셀렉터에 집중해 살펴보도록 하겠다. :last 셀렉터는 Selector API 표준을 jQuery 전용으로 확장한 것이다. 따라서 이 셀렉터는 네이티브 .querySelectorAll 메서드로는 사용할 수 없다.

Sizzle 은 기본적으로 요청받은 셀렉터를 .querySelectorAll 로 동작시켜본 후, 올바르지 않은 셀렉터로 인한 예외가 발생하면 순수 JavaScript 기반으로 DOM 선택, 필터링 한다.

즉 :last 등이 포함되면 네이티브 DOM 셀렉션의 빠른 속도를 얻을 수 없게 된다.

Furthermore, there are optimizations included so that when your selector is very simple, like just an ID or an element name, the native getElementById and getElementsByTagName will be used, which are extremely fast; usually even faster than querySelectorAll. And since the .last() method just grabs the last item in the collection instead of filtering all the items, which is what Sizzle filters normally do (at least they used to), that also will give a boost.

게다가 셀렉터가 ID나 태그이름만 사용된 경우처럼 아주 간단하다면 정말 빠른 네이티브 getElementById 와 getElementsByTagName 을 사용하도록 최적화 로직이 내장되어있다. . 이들은 일반적으로 querySelectorAll 보다 훨씬 빠르다.

그리고 .last() 메서드는 일반적인 Sizzle 의 필터처럼 모든 아이템 중에서 필터링하는 것이 아니라 그저 컬렉션에서 마지막 아이템을 가져오는 형태로 동작하므로 여기서도 이점을 찾을 수 있다.

IMO, keep away from the proprietary stuff. Now that .querySelectorAll is pretty much ubiquitous, there are real advantages to only using standards-compliant selectors. Do any further filtering post DOM selection. In the case of $(“#vacations”).find(“li”), don’t worry about the interim results. This will use getElementById followed by getElementsByTagName, and will be extremely fast. If you’re really super concerned about speed, reduce your usage of jQuery, and use the DOM directly.

개인적으로는 jQuery 전용 셀렉터를 배제하는 것이 좋겠다고 생각한다. .querySelectorAll 의 쓰임새가 훨씬 많은데, 표준 셀렉터를 사용해야만 그 이점을 누릴 수 있다. 그 이후의 필터링들은 DOM 셀렉팅 이후에 진행하도록 하자.

$(“#vacations”).find(“li”) 의 경우는 성능에 대해 크게 고민하지 않아도 된다. 이들은 getElementById 후에 getElementsByTagName 을 사용할 것이고, 이들은 정말 빠르기 때문이다.

진정으로 속도에 대해 고민한다면 jQuery 를 사용하지 말고 DOM 에 직접 접근하는 것이 좋다.

You’ll currently find notes in the docs for selectors like :last, that warn you about the performance loss:

Because :last is a jQuery extension and not part of the CSS specification, queries using :last cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :last to select elements, first select the elements using a pure CSS selector, then use .filter(“:last”).

But I’d disagree that .filter(“:last”) would be a good substitute. Much better would be methods like .last() that will target the element directly instead of filtering the set. I have a feeling that they just want people to keep using their non-standards-compliant selectors. IMO, you’re better of just forgetting about them.

jQuery 문서에도 :last 와 같은 셀렉터들의 성능문제에 대한 내용이 추가되었다.

:last 는 CSS 명세에 포함되지 않은 jQuery 의 확장이므로, :last 쿼리를 사용하는 것은 네이티브 DOM querySelectorAll() 의 성능향상을 기대할 수 없다. :last 를 사용해 요소를 선택할 때 최고의 성능을 얻으려면 먼저 순수 CSS 셀렉터로 요소를 선택하고, .filter(“:last”) 를 사용하라.

하지만 .filter(“:last”) 가 좋은 대체물이라고 생각하지는 않는다. .last() 와 같은 메서드들이 컬렉션을 필터링하기보다는 곧바로 요소를 잡아낼 수 있어서 훨씬 낫다. 내 느낌엔 이들이 사람들이 계속 비표준 셀렉터를 사용하길 원하고 있는 것 같다. 그러니 이건 그냥 잊는게 좋다고 생각한다.

그러니 jQuery 셀렉터보다는 표준 CSS 셀렉터를 숙지해 사용하도록 하자 🙂