HTML5에서 추가된 Geolocation API는 웹 브라우저에서 사용자 위치를 찾을 수 있도록 도와주는 API다. 이 API를 기반으로 웹앱을 만들거나 웹사이트에서 기능을 구현하면 임의의 경도, 위도로 변경해 테스트를 해야 하는 경우가 있는데 Google Chrome 개발자 도구에서 이 Geolocation 위치를 변경하는 기능을 지원한다.

개발자 도구에서 스마트폰 모양 아이콘을 클릭해 emulation 모드를 활성화 한 후, 하단 emulation 탭에서 좌측 Sensors 항목을 클릭하면 Emulate geolocation coordinates 항목을 확인할 수 있다.

emulate geolcation coordinates

좌표를 입력하면 API에서의 좌표가 변경된 것을 볼 수 있다.

geolocation result

CoffeeScript에 대한 얘기는 정말 많이 들었고 주변에서도 많이 사용하고 있지만 정작 제대로 살펴본 적이 없었다. 주말 시간을 내서 Better CoffeeScript Testing With Mocha 글을 중점으로 여러 아티클을 읽어보고 정리하는 차원에 남기는 포스트다.

이 모든 내용을 포스트 하나에서 다루기에는 각각의 내용이 방대하기 때문에 개론적으로만 읽고 각 소제목에 따른 더 읽을 거리를 참고 했으면 좋겠다.

BDD (Behavior-Driven Development)

BDD 즉, 행위/행동 주도 개발은 애자일 방법론 중 하나로 TDD와 인수 테스트 주도 계획(Acceptance Test-Driven Planning)에서 진화한 방법론이다. TDD를 한다는 본질은 다르지 않지만 사용자 스토리와 같은 인수 테스트를 기준으로 기획하게 된다.

유닛테스트는 올바른 코드를 작성했는가 즉, 개개의 클래스와 메소드를 테스트 한다면 인수테스트는 올바른 소프트웨어를 작성했는가 즉, 시스템 전반에 걸친 기능에 대해 테스트하게 된다. 그 결과로 Domain-Driven Design과 같이 기획과 개발에서 같은 시나리오로 이야기 할 수 있게 되고, 개발에서는 사용자 스토리의 맥락을 통해 요구사항을 쉽게 이해할 수 있게 된다.

더 읽어볼 만한 내용은 마지막 섹션인 더 읽을 거리에서 찾아볼 수 있다.

CoffeeScript

CoffeeScript는 JavaScript로 컴파일 할 수 있는 작은 언어다. JavaScript에서 반복적으로 사용되는 코드를 효과적으로 짧게 사용할 수 있도록 도와주고 그로 인해 가독성도 높아지는 등 여러 장점이 있어 많은 사람들이 사용하고 있다. 공식 사이트를 보면 알겠지만 축약 문법을 제공한다고 생각하면 이해하기 쉽다.

nodejs가 설치되어 있다면 npm으로 간단하게 설치해서 사용할 수 있다.

npm install -g coffee-script

js파일을 node에서 실행해볼 때 node something.js 와 같이 실행한다면 CoffeeScript에서는 coffee something.coffee 처럼 실행할 수 있다. 앞서 이야기 한 것처럼 CoffeeScript는 JavaScript로 컴파일되어 실행되는데 js파일로 어떻게 컴파일되는지 확인하고 싶다면 --compile 플래그를 사용해 js파일을 확인해볼 수 있다.

coffee --compile something.coffee
# something.js 파일로 결과물이 나온다

CoffeeScript의 문법은 CoffeeScript 웹사이트에서 확인할 수 있다.

Mocha, Chai

Mocha는 JavaScript 테스트 프레임워크로 node, 브라우저, 비동기 테스트까지 가능하다. 순수하게 테스트를 위한 프레임워크라서 assertion을 위해 다른 라이브러리를 사용해야 한다. 이 포스트에서는 chai BDD/TDD assertion 라이브러리를 사용한다.1

커피 세 잔으로 BDD하기

내가 읽은 글은 Todo 만들기였는데 이 포스트에서는 서점 만들기2를 해보려고 한다. 앞서 언급한 세 모듈을 로컬에만 설치해서 node_modules/.bin/coffee <blarblar> 식으로 사용해도 되지만 그냥 전역으로 설치했다.

$ npm install --global coffee-script mocha chai

먼저 npm initpackage.json을 생성하고 앞서 세 모듈을 의존성 모듈로 추가한다.

$ mkdir bookstore
$ cd bookstore
$ npm init
$ npm install coffee-script --save
$ npm install mocha chai --save-dev

Mocha는 기본적으로 test 폴더 내에 있는 파일들을 실행한다. test 폴더를 생성하고 bookstore_test.coffee를 추가해 테스트를 작성한다.

$ mkdir test
$ touch test/bookstore_test.coffee

Chai는 BDD와 TDD 모두 지원하는데 BDD 스타일로 사용하려면 chai를 불러온 후 chai.should()를 호출해야 한다. 다음과 같이 describeit으로 테스트 파일을 작성한다.

chai = require 'chai'
chai.should()

{Book} = require '../src/bookstore'

describe 'Book', ->
  it 'should have a title', ->
    book1 = new Book 'Colorless Tsukuru Tazaki'
    book1.title.should.equal 'Colorless Tsukuru Tazaki'

위 파일에서 assert 라이브러리 chai를 불러오고 Book 클래스를 앞으로 작성할 파일인 bookstore.coffee에서 불러왔다. 그리고 책은 제목을 가져야 한다는 시나리오를 작성하고 그에 맞는 테스트를 추가했다. 이제 프로젝트 디렉토리로 이동해서 mocha로 테스트를 실행한다.

$ mocha
0 passing (1ms)

mocha는 실행되었지만 작성한 파일이 실행되지 않았다. 만약 여기서 Error: cannot resolve path (or pattern) 'test'가 난다면 프로젝트 최상위 위치가 아니기에 test 디렉토리를 찾지 못해 발생하는 에러다.

여기서 mocha를 별다른 플래그 없이 실행하면 기본값인 JavaScript 파일만 확인해서 실행이 된다. 이 테스트가 CoffeeScript로 작성되어 있으므로 --compiler 플래그를 다음과 같이 추가해야 한다.

$ mocha --compilers coffee:coffee-script/register
  Book
    1) should have a title

  0 passing (4ms)
  1 failing

  1) Book should have a title:
     ReferenceError: Book is not defined
     ...

작성한 코드가 실행되었고 아직 Book을 작성하지 않았기 때문에 실패했다. (위 코드에서 보듯 ReferenceError가 발생했다.) src 디렉토리에 bookstore.coffee를 추가하고 코드를 작성한다.

class Book
  constructor: (@title) ->
    true

root = exports ? window
root.Book = Book

CoffeeScript는 컴파일되면 익명함수 속으로 들어가서 실행되기 때문에3 마지막 두 줄을 추가해 다른 곳에서도 해당 클래스를 사용할 수 있도록 해야 한다.

이제 파일을 작성했으니 mocha를 실행해야 한다. mocha를 실행할 때 --compiler 플래그 등의 설정값을 설정파일을 만들어서 대체할 수 있다. test 폴더 내에 mocha.opts 파일을 만들고 해당 플래그를 추가한다.

--compilers coffee:coffee-script/register

이제 mocha를 실행하면 다음과 같이 테스트가 성공하는 것을 확인할 수 있다.

$ mocha
  Book
    ✓ should have a title 

  1 passing (3ms)

여기서 책은 새 책/헌 책 일 수 있다, 책은 시리즈물 일 수 있다는 행동을 추가해 다음과 같이 작성했다.

describe 'Book', ->
  book1 = book2 = null

  it 'should have a title', ->
    book1 = new Book 'Colorless Tsukuru Tazaki'
    book1.title.should.equal 'Colorless Tsukuru Tazaki'

  it 'should be a series of books', ->
    book1 = new Book "Harry Potter and the Philosopher's Stone"
    book2 = new Book "Harry Potter and the Chamber of Secrets"

    book2.seriesOf book1

    book2.volume.should.equal 'series'
    book2.original.should.equal book1
    book1.sequel.should.equal book2

  it 'should be initially new', ->
    book1.status.should.equal 'new'

  it 'should be used', ->
    book1.use().should.be.true
    book1.status.should.equal 'used'

위 테스트를 통과할 수 있도록 seriesOf, use 메소드와 volume, status 프로퍼티를 추가해 Book 클래스를 작성하면 다음과 같다.

class Book
  constructor: (@title) ->
    @volume = 'single'
    @status = 'new'
    true

  seriesOf: (@original) ->
    @original.sequel = @
    @volume = 'series'

  use: ->
    @status = 'used'
    true

테스트를 실행하면 테스트를 모두 통과하는 것을 볼 수 있다.

$ mocha

Book
  ✓ should have a title 
  ✓ should be a series of books 
  ✓ should be initially new 
  ✓ should be used 

4 passing (3ms)

직접 작성해보기

어떤 과정으로 프로젝트를 시작하고 BDD를 통해 개발을 하는지 간단하게 확인했다. 원 글에서는 다른 클래스에 대해서도 설명했지만 같은 내용을 반복하는 것보다 추가적인 과정은 직접 해보고 리포지터리랑 비교해보는게 좋을 것 같아서 여기까지만 작성했다.

Bookshelf는 다음과 같은 시나리오로 작성을 했다.

  • 책장은 처음에는 비어 있을 것이다.
  • 책장은 새 책을 넣을 수 있을 것이다.
  • 책장은 새 책 제목 만으로도 새 책으로 넣을 수 있을 것이다.
  • 책장은 책을 뺄 수 있을 것이다.
  • 책장은 모든 책 목록을 보여줄 수 있을 것이다.

Mochachai로 다양한 시나리오를 작성해보고 그에 맞는 코드를 CoffeeScript로 작성해보는 것은 분명 도움이 되리라 믿는다. 내가 작성한 최종 코드는 이 리포지터리에서 확인할 수 있다.

더 읽을 거리

Better CoffeeScript Testing With Mocha는 위 내용보다 더 세세하게 다루고 있어서 이 글 보다 이해하기 쉽다.

BDD에 대해서 더 알고 싶다면 다음 글이 도움이 된다.

여기서 사용한 모듈은 전부 문서화가 잘 되어있는 편이었다.

  • 이름만으로는 도대체 무슨 역할을 하는지 알 수 없는 시대다. 커피 이름 종류별로 다 쓰고 나면 나중엔 WhiteChocolateMochaFrappuccino 같은 이름을 써야할지도. 

  • <li id="fn-2621-2">
      결국은 같은데 이름만 다른 코드 아니냐 하시면 할 말은 없습니다. 녜. (&#8230;)&#160;<a href="#fnref-2621-2" rev="footnote">&#8617;</a>
    </li>
    <li id="fn-2621-3">
      <code>coffee -c filename.coffee</code> 명령어로 어떻게 컴파일이 되는지 눈으로 확인하면 이해가 쉽다.&#160;<a href="#fnref-2621-3" rev="footnote">&#8617;</a> </fn></footnotes>
    

    최근에 #이상한모임 slack이 개설되었다. 순식간에 많은 분들이 가입해서 왕성한 활동 펼치고 있어 신기해 하는 한편 같이 휩쓸려(?) 잘 놀고 있다. 골빈해커님이 hubot으로 weirdbot을 만들어서 재미있는 기능들을 만드는 것을 보고 hubot을 어떻게 만들 수 있는지 찾아보게 되었다.

    Hubot은 GitHub에서 공개한, 자동화를 위한 채팅봇이다. Node.js 기반에 CoffeeScript로 짜여져 있고 Heroku와 같은 곳에 손쉽게 디플로이 해서 활용 가능하며 확장도 간편하게 가능하도록 구성되어 있다. 또한 이미 많은 기능들이 구현되어 있어서 npm에서 hubot-script을 내려받고 설정하는 것으로도 강력하게 활용할 수 있다.

    Slack은 협업을 위한 채팅 도구로 GitHub, Trello 등 다양한 Integration, 여러 체널의 Push notification 지원 등 편리한 기능을 많이 제공한다. 이전까지는 몰랐는데 내부적으로는 IRC로 구현되어 있는 모양이다. IRC에 친숙하다면 쉽게 적응해서 사용할 수 있을 정도로 유사한 부분이 많다.

    Slack에서 Integration을 통해 Hubot을 추가하면 API키를 발급해줘서 쉽게 연동이 가능한데 무료 정책 내에서는 연동할 수 있는 서비스 수가 제한되어 있기 때문에 여기서는 IRC gateway를 활용했다. IRC Gateway는 보안상 기본 설정에서는 사용할 수 없다. 사용하려면 slack의 primary owner에게 요청해 기능을 사용할 수 있도록 활성화 해야 한다. 다만 이 gateway를 사용하게 되면 채팅방의 내용이 외부로 노출될 가능성이 있다는 요지의 안내를 받을 수 있는데 보안성에 민감한 slack이라면 이 방법보다 앞서 얘기한 hubot integration을 사용하는 편이 낫다.

    이 글에서는 다음과 같은 방법으로 Hubot을 만들어 사용했다. (늘 그렇듯 OSX 기준으로 작성되어 있다.)

    • 로봇을 돌리기 위한, slack 일반 계정을 생성
    • IRC Gateway 사용
    • Heroku 사용

    Slack 계정 생성하기

    본인 그룹의 slack에서 IRC Gateway 사용이 가능하다면 로봇으로 상주시킬 계정을 새로 생성한 후 Account > Settings > Gateways 에서 Gateway Configuration 버튼을 눌러 Host, User, Pass 정보를 확인한다.

    Hubot 설치하기

    사실 이런 포스트 할 필요도 없을 정도로 Hubot 문서는 깔끔하게 잘 되어 있다. 먼저 기본적으로 nodejs는 설치되어 있어야 한다.

    $ npm install -g yo generator-hubot
    $ mkdir koalabot
    $ cd koalabot
    $ yo hubot
    

    [yeoman](http://yeoman.io/) 제너레이터로 hubot을 생성할 수 있다. yo hubot 명령어를 입력하면 봇을 생성하는 인터프리터가 나타나는데 내용에 맞게 작성자, 봇 이름, 설명 등을 순차적으로 입력하면 된다. 여기서는 IRC Gateway를 사용하므로 adapter에는 irc을 입력하면 된다. 그러면 알아서 irc에 맞는 어뎁터가 설치된다.

    yo hubot

    여기서 unicode/ucsdet.h를 찾을 수 없다는 에러가 나타나면 다음 명령어로 command line developer tool을 설치한다.

    $ xcode-select --install
    

    이제 초기 생성이 끝났다. 이제 설치된 hubot을 shell adapter를 통해 콘솔에서 확인할 수 있다. hubot help를 입력하면 사용 가능한 명령어를 볼 수 있다.

    $ bin/hubot --name <봇이름>
    

    Heroku로 디플로이 하기

    이제 heroku로 봇을 내보낼 차례다. heroku를 사용하기 위해서는 heroku에 먼저 가입을 해야 하고 heroku toolbalt를 설치해야 한다. 그리고 나서 생성한 hubot을 git에 커밋한다.

    $ git init
    $ git add .
    $ git commit -m "Initial commit"
    

    앞서 가입한 heroku를 heroku login을 통해 로그인하고 dyno라고 불리는 컨테이너를 생성한 후 환경설정을 한 다음에 앞서 만들었던 hubot을 내보내는 순서로 진행된다.

    $ heroku login
    # 앞서 가입한 정보로 로그인
    $ heroku create
    # heroku에 dyno가 생성됨, dyno 주소를 알려준다.
    
    # 환경변수를 다음과 같이 지정하는데 서버, 닉네임, 비밀번호는 앞서 IRC Gateway에서의 정보를 입력해야 한다.
    $ heroku config:set HUBOT_IRC_SERVER="<IRC Server>"
    $ heroku config:set HUBOT_IRC_NICK="<IRC Nickname>"
    $ heroku config:set HUBOT_IRC_PASSWORD="<IRC Password>"
    
    # 자동으로 접속하게 될 방 목록을 `,`로 구분해서 입력한다.
    $ heroku config:set HUBOT_IRC_ROOMS="#koala,#kangaroo,#melbourne"
    $ heroku config:set HUBOT_IRC_UNFLOOD="false"
    $ heroku config:set HUBOT_IRC_USESSL=1
    
    # dyno가 생성될 때 알려준 주소를 입력한다.
    $ heroku config:set HEROKU_URL=http://hot-koala-2015.herokuapp.com
    
    # heroku로 git을 push하면 끝난다.
    $ git push heroku master
    

    Hubot 기능 추가하기

    Hubot 스크립트 설치

    hubot-scripts에 포함되어 있는 스크립트는 hubot-scripts.json에 추가하는 것으로 바로 사용할 수 있다. 사용 가능한 목록은 카탈로그 페이지에서 확인할 수 있다.

    외부 스크립트 설치

    외부 스크립트 설치도 지원한다. 다음은 hubot-thank-you를 설치하는 예로 아래와 같이 npm으로 설치한 후 external-scripts.json에 배열로 해당 스크립트명을 추가하면 된다.

    $ npm install hubot-thank-you --save
    # external-scripts.json을 열어서 "hubot-thank-you"를 json스럽게(...) 추가한다.
    

    직접 만들기

    직접 만들고 싶다면 scripts 폴더에 있는 example.coffee를 참고해 만들면 된다. 자세한 가이드는 Scripting 항목에서 볼 수 있다.


    아무리 재미있는 도구들이 나와도 회사에서 적용하지 않는 경우엔 둘러보는 정도로 그칠 때가 많았는데 이 기회에 살펴볼 수 있어서 좋았다. CoffeeScript는 아직도 어색한데 익숙해질 수 있도록 좀 더 살펴봐야겠다.

    요즘 대부분의 php 도구들이 콘솔에서 사용할 수 있도록 제공되고 있다. OSX에는 기본적으로 php가 설치되어 있고 별다른 설정이 없다면 이 php를 사용하게 된다. composer 같은 도구는 php 버전이나 모듈과는 큰 영향이 없어서 기본 설치 과정을 따라해도 큰 문제가 없지만 데이터베이스 연결이 필요하거나 하는 경우에는 문제가 발생할 수 있다.

    MAMP 환경을 설치해서 개발에 사용하고 있다면 이 MAMP에서 사용하고 있는 php를 간단하게 연결해서 활용할 수 있다.

    이와 관련해서 검색해보면 직접 컴파일해서 패키지를 설치하라거나 mamp-php 등의 이름으로 심볼릭 링크를 연결해주는 등 여러 방법이 있었는데 다 장단점이 있었고 가장 간단하고 별 문제 없는 방식이 기존 설치되어 있는 php를 mamp 안에 있는 php로 심볼릭 링크를 생성해주는 방법이었다.

    먼저 php가 설치되어 있는 위치를 찾는다.

    $ which php
    /usr/local/opt/php56/bin/php
    

    내 경우는 brew로 php5.6을 설치해 경로가 다른데 /usr/local/bin/php 쯤 될 것이다. 파일명을 변경하고 심볼릭 링크를 생성한다.

    $ cd /usr/local/opt/php56/bin/
    $ mv php php_backup
    $ ln -s /Applications/MAMP/bin/php/php<사용하는 php version>/bin/php php
    

    이렇게 변경하면 기본 php를 mamp에서 사용하는 php로 사용할 수 있다.


    2015년 1월 20일 추가.

    환경변수를 추가해주는 방법도 있었다. (왜 이걸 생각하지 못했는지 ㅠㅠ) 다음 두 값을 zsh 사용자는 .zshrc, bash 사용자는 .bash_profile에 추가하면 된다.

    export MAMP_PHP=/Applications/MAMP/bin/php/php<사용하는 php version>/bin
    export PATH="$MAMP_PHP:$PATH"
    

    환경변수에 선언되어 있는 순서대로 명령어를 실행할 수 있는 프로그램이 있는지 확인한다. 정상적으로 연결되어 있는지는 앞서 사용했던 which 명령어로 확인할 수 있다.

    최근 회사 프로젝트에서 C# 어플리케이션을 obfuscate 하면서 알게 된 부분들을 정리한 포스트.

    내 (얕은) 지식으로는 컴파일 언어는 “컴파일러를 통해 바이너리로 치환되서 컴파일된 결과물만 가지고 소스를 복구할 수 없다”고 알고 있었는데 현대 언어에서는 그게 그렇게 단순한 부분이 아닌 것 같다. C# 프로그램은 dll이든 exe든 디컴파일러를 통해 내부 구조를 볼 수 있다. 심지어 그런 디컴파일러가 불법적으로 암암리에 유포된다거나 하는 것이 아니라 무료로 공개되어 있기도 하고 다들 많이 사용하는 모양이다. (예를 들면, Jetbrain의 dotpeek.)

    컴파일 언어인데 어떻게 가능하지

    C# 왕초보라 정확한 설명인지는 잘 모르겠지만 이것저것 찾아서 읽어본 글에 따르면,

    • C#은 Common Language Runtime(CLR) 위에서 돌아가는 언어
    • CLR은 MS에서 Common Language Infrastructure(CLI)를 구현한 구현체
    • CLI 규격에 의해 C#도 메타데이터를 포함

    C#의 경우 이 메타데이터가 가지는 장점으로 쉽게 reflection 가능한 구조로 되어 있고 이를 통해 어셈블리의 타입이나 멤버, 메소드, 심지어는 내부 로직까지도 들여다볼 수 있다고 한다.

    좋은 일에 쓴다면 엄청난 효율을 만드는 장점이지만 내부 로직이 공개될 위험이 있다. 그래서 디컴파일을 방지할 수 있는가에 대한 답변을 SO에서 찾아봤는데 대부분 답변이 둘로 수렴했다. 하나는 obfuscate 난독화를 하는 것이고, 다른 하나는 PaaS로 서비스하라는 얘기였다. 현재 프로젝트는 장기적으로 PaaS로 가겠지만 일단은 전자의 방식을 사용하기로 결정했다.

    C# 난독화

    Obfuscation은 코드를 읽기 어렵게 만들어서 내부 로직을 쉽게 들여다볼 수 없게 만드는 과정이다. 메소드, 변수 등을 a, b, c 등으로 수정해서 무슨 로직으로 동작하는지 알아보기 어렵게 만드는 것이다. 목적은 다르지만 결과물로 보면 JavaScript에서 js minify 한 것과 비슷하다.

    Obfuscate하는 도구는 시중에 엄청 많이 나와 있는데 Visual Studio 2012를 설치하면 포함되어 있는 Dotfuscator를 사용했다. Dotfuscator의 사용 방법은 파일 추가 > 결과물 위치 지정 > 실행, 3단계로 아주 단순하다.

    dotfuscator

    다만 Dotfuscator를 하게 되면 모든 클래스, 메소드 등 대부분의 명칭이 a, b, c 와 같이 변하기 때문에 문제가 되는 부분이 있을 수 있다. 가령 JSON을 사용하는 경우라면 Serialize 될 때 해당 멤버변수명을 그대로 사용해야 한다. 그런 경우 다음과 같이 attribute로 예외를 지정해주면 된다.

    class PolicyContainer
    {
        [Obfuscation(Feature = "renaming", Exclude = true)]
        public IList<POLICY> Policys;
    ...
    

    위 attribute와 같이 작성하면 해당 변수는 obfuscate되지 않는다. ObfuscationAttribute Class에서 더 자세한 내용을 확인할 수 있다.

    PHP를 대차게 까는 분들이 가끔 워드프레스 설치하는 환경을 물어보기도 하고 또 환경 설정을 알려주면 설치하면서도 잔소리를 계속 하길래 이런 도구를 소개하는 것도 도움이 될 것 같아 짧게 소개글을 남긴다.

    이제는 일반적인 웹호스팅 비용이나 AWS, Azure와 같은 IaaS의 요금이랑 큰 차이가 없어지고 있는데다 오히려 후자가 저렴한 경우도 많다. 게다가 서버 환경 설정을 마음대로 할 수 없고 해당 호스팅 업체에서 제공하는 서비스가 제한적일 때도 많아서 자연스럽게 가상 서버 환경을 염두하게 된다.

    요즘은 이미 호스팅 환경이 설치되어 있는 서버 이미지를 제공하는 경우도 있고 앱 컨테이너로 쉽게 올릴 수 있는 플랫폼을 제공하는 경우도 많지만 웹호스팅에서 옮겨오려고 고민하는 케이스에는 굉장히 거창하고 막막할 수 밖에 없다. 겨우 필요한건 MySQL, Apache, PHP 환경인데 1) 안전하고 제대로 설치하는게 맞는지, 2) 튜토리얼이 엄청 많은데 어느 글을 따라해야 하는지, 3) 매번 콘솔에서 작업해야 하는지 등 고민이 줄줄이 나온다.

    이 문제를 간단히 해결해줄 호스팅 관리도구 VestaCP가 있다. (No strings attached.)

    명령어 2줄로 LAMP 환경을 모두 설치해주고 함께 설치되는 관리자 화면은 아주 심플한데다 국내 웹호스팅에서 제공하는 기능 정도는 다 제공한다. (SSL 설정, cron 설정 같은 것도.) 추가적인, 세세한 설정이 필요하다면 여전히 콘솔을 통해 작업을 해야 하지만 워드프레스 사이트를 운영한다던지, 간단한 LAMP 환경 개발 같은 경우에는 별도의 수정 없이도 충분히 사용이 가능했다.

    설치는 다음과 같이 가능하다.

    # Download installation script
    curl -O http://vestacp.com/pub/vst-install.sh
    # Run it
    bash vst-install.sh
    

    AWS Free-tier를 사용해도 문제 없이 잘 돌아간다. EC2 Ubuntu 이미지라면 다음과 같이 설치해야 한다.

    # Download installation script
    curl -O http://vestacp.com/pub/vst-install.sh
    # Run it
    sudo bash vst-install.sh --force
    

    회사에서 cPanel (&WHM)과 Plesk를 사용하고 있었다. 이 둘은 엄청나게 세세하고 다양한 기능을 제공하긴 하지만 최근 작업한 magento 기반 프로젝트들은 다른 사이트와 함께 쓰기에는 무거운 감이 있어서 별개의 가상서버를 사용하게 되었고 기존 사용하던 관리도구들이 비용 문제, 고사양이 필요한 문제가 있었다. 그래서 대안을 찾다 VestaCP를 알게 되었고 최근 구축한 사이트는 다 이 관리도구를 이용한 서버에서 운영하고 있다.

    추가적인 설정이 필요한 경우 Vesta 문서를 참고하자.

    ConfigurationManager.appSettingsSerialize해서 다른 곳에 전송하는 것은 어떨까 하는 아이디어를 듣고 코드를 작성해 Json.NET을 사용해서 SerializeObject를 했다. appSettings는 NameValueCollection 클래스인데 Dictionary와 같이 serialize 될 것이라 예상했지만 결과는 키값만 배열로 반환했다.

    var col = new System.Collections.Specialized.NameValueCollection(){
        {"a", "Hello"}, {"a", "World"}
    };
    
    Console.WriteLine(JsonConvert.serializeObject(col));
    // return "[\"a\"]"
    

    NameValueCollection은 하나의 키에 여러개의 값을 가질 수 있는 컬렉션이기 때문에 Dictionary와는 다른 형태로 serialize되도록 NameObjectCollectionBase에서 구현되어 있는 것으로 보인다.

    Console.WriteLine(col["a"]);
    // return Hello,World
    

    동일한 키라도 각각의 값이 독립적으로 보장되야 한다면 조금 복잡해지겠지만 내 경우에는 위와 같이 ,로만 구분 되어도 큰 문제가 없는 상황이라서 Dictionary로 변환한 후에 Serialize하는 방법으로 문제를 해결했다.

    var dict = col.AllKeys.ToDictionary(k => k, v => col[v]);
    
    Console.WriteLine(dict["a"]);
    // return Hello,World
    
    Console.WriteLine(JsonConvert.serializeObject(dict));
    // return "{\"a\":\"Hello,World\"}"
    

    동일한 키에 여러개의 값을 가지는 상황이라면 JSON에선 키 아래 배열로 변환되어야 의미론에 더 맞는 것 같다. 위와 같은 방법 말고도 더 아름답고 쉽고 시멘틱한 방법이 있을 것 같은데…


    GetValues(key)를 이용하면 string[]으로 반환해준다고 한다. how to convert NameValueCollection to JSONstring의 코드를 참고하면 되겠다.

    C#을 쓸 일이 종종 있는데 아직 초보 수준이라서 모르는 문법이 많다. 코드를 읽다가 메서드 선언 앞에 나온 물결 문자를 보게 되었는데 관련된 내용을 찾아봤다. 다음 내용은 함수명 앞에 오는 물결 표시는 무슨 의미인가요?에 나온 답변이다.

    C#에서 메소드 선언 앞에 나오는 ~(물결 문자, 틸드)는 소멸자다.

    • 소멸자는 자동으로 동작하며 명시적으로 실행할 수 없다.
    • 소멸자는 오버로드가 불가능하다. 그러므로 클래스는 최대 하나의 소멸자를 가질 수 있다.
    • 소멸자는 상속되지 않는다. 그러므로 클래스는 하나 이외의 소멸자를 가지지 않게 되고 그 소멸자는 해당 클래스에서 선언이 되어야만 한다.
    • 소멸자는 구조체에서는 사용할 수 없다. 클래스에서만 사용 가능하며 어떤 코드에서도 더 이상 인스턴스를 사용하지 못하게 될 때 소멸될 자격이 주어진다.
    • 인스턴스의 소멸자는 인스턴스가 소멸될 자격만 된다면 언제든 실행이 된다.
    • 인스턴스가 소멸될 때 소멸자는 상속 체인을 따라 자식-부모 순으로 순차적으로 호출된다.

    Finalize

    C#에서 Finalize 메소드는 표준 C++ 소멸자처럼 동작한다. C#에서는 Finalize라고 메소드에 이름 붙이는 것 대신 C++의 소멸자 문법처럼 물결 표시를 메소드명 앞에 붙인다.

    Dispose

    객체를 정리하기 위해 명시적으로 호출이 가능한 메소드 Close() 또는 Dispose()를 제공하는 것이 바람직하다. 소멸자는 GC에 의해 호출되기 때문이다.

    IDisposable 인터페이스는 클래스가 가지고 있는 자원이 정리되어야 한다는 것을 알려주며 실제로 정리될 수 있는 방법을 제공해준다. 만약 소멸자를 직접 구현해야 한다면 그 클래스의 정리(Dispose) 메소드는 GC.SuppressFinalize() 메소드를 꼭 실행해 인스턴스를 강제적으로 제거해야 한다.

    What to use?

    명시적으로 소멸자를 호출하는 것은 허용되지 않는다. 소멸자는 가비지 컬렉터에 의해 호출된다. 만약 file을 다루는 것과 같이 처리 비용이 비싸 관리되지 않는 자원들을 다루게 되면 가능한한 빠르게 닫고 정리하기를 원할 것이다. 이런 경우에는 IDisposable 인터페이스를 구현해야 한다.


    정말 상속되지 않는가

    MSDN의 Destructors (C# Programming Guide)의 예제를 보면 상속은 아니지만 상속 순서에 따른 연쇄 호출(chaining)이 발생한다. 호출의 순서는 가장 하위 클래스의 소멸자부터 상위 클래스로 연쇄적으로 호출한다.

    class First
    {
        ~First()
        {
            System.Diagnostics.Trace.WriteLine("First's destructor is called.");
        }
    }
    
    class Second : First
    {
        ~Second()
        {
            System.Diagnostics.Trace.WriteLine("Second's destructor is called.");
        }
    }
    
    class Third : Second
    {
        ~Third()
        {
            System.Diagnostics.Trace.WriteLine("Third's destructor is called.");
        }
    }
    
    class TestDestructors
    {
        static void Main()
        {
            Third t = new Third();
        }
    
    }
    /* Output (to VS Output Window):
        Third's destructor is called.
        Second's destructor is called.
        First's destructor is called.
    */
    

    트위터, 페이스북, pocket 전부 관리되지 않는 스크랩 자료로만 가득 차는 기분이 들어서 워드프레스에서 수집할 수 있도록 작은 플러그인을 만들었다.

    플러그인은 scrap이라는 포스트 타입을 생성해주며 일반 포스트와 동일하게 category와 tag를 지원한다. 추가적으로 워드프레스에서 post에서 사용할 수 있는 함수를 비슷하게 쓸 수 있도록 이름만 바꿔서 같이 넣었다. 기본적인 widget도 포함하고 있다.

    플러그인은 gist 에서 받을 수 있으며 소스코드도 확인 할 수 있다.

    2014년 10월 7일 업데이트: 플러그인을 GitHub 리포지터리로 옮기고 bookmarklet을 활용할 수 있도록 Scrap this를 추가했다. 다운로드는 여기에서, 코드는 github에서 확인할 수 있다.


    이 블로그와 같이 scrap archive를 만들기 위해서는 해당 테마에 새로운 포스트 타입과 taxonomy를 위한 페이지를 생성해야 하며 생성할 때 the_tag()와 같은 함수들을 the_scrap_tag()와 같이 변경해줘야 한다.

    • archive.php -> archive-scrap.php, taxonomy-scrap_category.php, taxonomy-scrap_tag.php
    • single.php-> single-scrap.php
    • sidebar.php -> sidebar-scrap.php

    물론 생성하지 않아도 출력은 되지만 약간 다르게 나오는 부분들이 있다. (원래는 자동으로 뿅 되야 하는데 대충 만들어서… 추후 버전을 기약하며.)

    이 플러그인을 위한 특정 Permalink를 사용하려면 Custom Post Type Permalinks 플러그인을 활용할 수 있다.

    객체 지향 프로그래밍에 익숙한 개발자라면 하나의 파일에 하나의 클래스를 작성하는 방식에 익숙할 것이다. 다만 php는 다른 언어와 같이 라이브러리를 일괄적으로 불러오는 방법이 없어 위와 같은 접근 방법으로는 require 또는 include를 이용해 수많은 단일 파일을 불러들여야만 했었다.

    PHP5에서는 클래스 또는 인터페이스 등을 호출했을 때 해당 파일을 자동으로 불러올 수 있도록 여러 함수를 제공한다. 먼저 __autoload 함수를 이용한 예제다.

    <?php
    function __autoload($className){
      include $className . '.php';
    }
    
    $foo = new Foo();
    $bar = new Bar();
    ?>
    

    위와 같이 함수를 선언하면 new Foo()와 같이 클래스를 사용하는 순간 해당 클래스명으로 __autoload 함수가 실행, Foo.php 파일을 include한다.

    다만 __autoload 함수는 spl_autoload_register 함수를 통해 대체될 수 있기 때문에 권장되지 않는다. spl_autoload_register는 다음과 같이 사용한다.

    <?php
    function my_autoloader($className){
      include 'classes/' . $className . '.class.php';
    }
    
    spl_autoload_register('my_autoloader');
    ?>
    

    PSR-0 Autoloading Standard

    위 함수를 통해 모듈화가 가능하도록 PHP Framework Interop Group(PHP-FIG)에서 PSR-0 Autoloading Standard가 제안되었다. 해당 제안은 다음의 규약을 포함하고 있다.

    • 네임스페이스와 클래스명으로의 자격을 갖추기 위해서는 다음의 구조를 따라야 한다. \<Vendor Name>\(<Namespace>\)*<Class Name>
    • 각 네임스페이스는 최상위 네임스페이스를 가져야 한다. (“Vendor Name”)
    • 각 네임스페이스는 필요에 따라 서브 네임스페이스를 가질 수 있다.
    • 각 네임스페이스 구분자는 파일 시스템에서 해당 파일을 불러오기 위한 디렉토리 구분자로 사용된다.
    • 클래스명에 들어있는 _ 글자도 디렉토리 구분자로 사용된다. 네임스페이스에서의 _는 특별한 의미가 없다.(PEAR 구현을 포함)
    • 완전한 네임스페이스와 클래스는 파일 시스템에서 불러올 때 .php를 접미어로 붙여 불러온다.
    • 알파벳으로 구성된 벤더명, 네임스페이스, 클래스명은 대소문자를 구분한다.

    위 규약에 따른 예제는 PSR-0 문서에서 제공되고 있으며 SplClassLoader 구현도 해당 문서에서 확인할 수 있다.

    다음은 문서에서 제공되는 autoload 함수 예제다.

    <?php
    function autoload($className) {
      $className = ltrim($className, '\\');
      $fileName = '';
      $namespace = '';
      if($lastNsPos = strpos($className, '\\')) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
      }
      $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
      require $fileName;
      }
    }
    ?>
    

    Composer 활용하기

    Composer는 PSR-4 Autoloader 제안과 함께 위에서 살펴본 PSR-0를 준수하고 있어서 간단한 설정으로 PSR-0 방식을 사용할 수 있다. composer.json에 autoload 경로를 등록하면 composer의 ClassLoader와 맵핑되어 자동으로 불러온다.

    {
      ...
      "autoload": {
        "psr-0": {"": "<path>/"}
      },
      ...
    }
    

    위 내용을 추가한 후 composer update 등을 통해 갱신하면 vendor/composer/autoload_namespaces.php 파일 안에 composer.json에서 작성한 경로가 추가된 것을 확인할 수 있다.

    더 읽을 거리

    색상을 바꿔요

    눈에 편한 색상을 골라보세요 :)

    Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.