Micro-framework의 전성기라고 할 만큼 다양한 환경과 언어로 프레임워크가 쏟아지고 있다. PHP에도 micro-framework가 많이 나와 있는데1 최근 Laravel에서 Lumen을 발표했다. 발표 자료에서는 symfony2 기반인 silex보다 1.9배 빠르다고 하는데 문법적으로는 Silm과 상당히 유사한 느낌도 든다. 기존에 나왔던 프레임워크와 엄청나게 큰 구조 차이를 가지고 있는 것은 아니지만 Laravel과의 호환을 염두한 부분도 많다는 느낌을 받았다. 또한 구조적으로도 silex나 여타 기존에 나온 micro-framework 보다 훨씬 깔끔하고 미려하다는 느낌을 받았다.

lumen logo

이 포스트는 lumen 문서에서 쉽게 볼 수 있는 부분만 다뤘고 더 깊은 내용을 보고 싶다면 Lumen 공식 문서를 보는게 도움이 된다. Lumen는 PHP >= 5.4를 요구하며 Mcrypt, OpenSSL, mbstring, tokenizer 확장을 필요로 한다.

Lumen 설치

lumen을 설치하기 위해서는 composer가 설치되어 있어야 한다.

lumen을 사용해 프로젝트를 시작하는 방법은 lumen installer를 사용하는 방법과 composer의 create-project로 생성하는 방법이 있다. 결과물은 동일한데 installer 속도가 더 빠르다.

composer.json, phpunit.xml 등 단순한 스캐폴딩을 함께 제공한다.

Lumen Installer

다음 명령어로 Lumen Installer를 설치한다. 이 installer는 커맨드라인 환경에서 lumen 프로젝트를 시작할 수 있도록 기능을 제공한다.

composer global require "laravel/lumen-installer=~1.0"

설치가 완료되면 lumen new 명령어로 프로젝트를 생성할 수 있다. 여기서 helloworld라는 이름으로 프로젝트를 생성했다.

lumen new helloworld

다음과 같이 프로젝트가 생성된 것을 확인할 수 있다.

lumen init

composer

위 인스톨러를 사용할 수 없다면 composer create-project 생성할 수 있다.

composer create-project laravel/lumen --prefer-dist

설정하기

lumen은 laravel과 다르게 모든 설정을 .env에 저장한다. 쉽게 활용할 수 있도록 .env.example 템플릿이 제공된다. 데이터베이스, 캐시, 큐, 세션과 관련한 설정값을 지정할 수 있다. 설정에서 가장 먼저 할 일로 해당 템플릿을 열어 APP_KEY에 32자 무작위 문자열을 입력한 후 .env로 저장한다.

URL 설정

Apache 환경을 사용하고 있다면 public/.htaccess를 활용할 수 있고 Nginx에서는 다음과 같이 설정할 수 있다.

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

HTTP 라우팅

라우팅 기초

route는 app/Http/routes.php에 작성한다. 여타 micro-Framework과 크게 다르지 않은 문법이다.

$app->get('/', function() {
  return 'Hello World';  
});
$app->post('foo/bar', function() {
  // do something
});
$app->patch('foo/bar', function() {
  // do something
});
$app->put('foo/bar', function() {
  // do something
});
$app->delete('foo/bar', function() {
  // do something
});

이렇게 생성한 route에서 URL을 다른 곳에서 사용하고 싶다면 url 헬퍼를 쓴다.

$list_link = url('foo');

router에서 파라미터는 다음과 같이 사용한다.

$app->get('user/{id}', function($id) {
  return 'User '.$id;
});

$app->get('user/{name:[A-Za-z]+}', function($name) {
  // do something
  // 이 문법은 라라벨과 호환되지 않는다.
});

컨트롤러와도 쉽게 연결할 수 있다.

$app->get('user/{id}', 'UserController@showProfile');

위 route와 같이 복잡한 URL 구조를 가지고 있다면 route에 as로 별칭을 지정해 쉽게 활용할 수 있다.

$app->get('user/profile', ['as' => 'profile', function() {
  // show user profile
}]);

$app->get('user/dashboard', [
  'as' => 'dashboard',
  'uses' => 'UserController@showDashboard'
]);

이제 위 route는 profile이라는 이름으로 활용할 수 있다. 파라미터가 있는 경우는 두번째 파라미터에 array로 값을 넣으면 된다.

$url = route('profile');
$redirect = redirect()->route('profile');

$profile_url = route('profile', ['id' => 1]);

라우팅 그룹 묶기

route를 그룹으로 묶어 미들웨어나 네임스페이스를 지정할 수 있다. 여기에서 $app->group()를 활용한다.

Closure를 기반으로 한 미들웨어는 다음과 같이 사용할 수 있다.

$app->group(['middleware' => 'foolbar'], function($app) {
  $app->get('/', function() {
    // do something
  });
  $app->get('user/profile', function() {
    // do something
  });
});

namespace로 특정 네임스페이스에 있는 컨트롤러를 불러 활용할 수 있다.

$app->group(['namespace' => 'Admin'], function() {
  // "App\Http\Controllers\Admin" 네임스페이스에 있는 컨트롤러
});

HTTP 예외 발생하기

abort 헬퍼를 이용한다. 응답을 같이 보내줄 수도 있다.

abort(404);
abort(403, 'Unauthorised action.');

이 헬퍼는 상태 코드와 함께 Symfony\Component\HttpFoundation\Exception\HttpException 예외를 던진다.

뷰는 resources/views에 php 파일로 저장한다.

<!-- View stored in resources/views/greeting.php -->

<!doctype html>
<html>
    <head>
        <title>Welcome!</title>
    </head>
    <body>
        <h1>Hello, <?php echo $name; ?></h1>
    </body>
</html>

다음과 같이 사용할 수 있다.

$app->get('/', function() {
  return view('greeting', ['name' => 'James']);
});

$app->get('/admin', function() {
  /* ... */

  // resources/views/admin/dashboard.php
  return view('admin.dashboard', $data);
});

데이터 바인딩을 배열로 넘길 수 있지만 with 메소드나 매직 메소드를 활용할 수도 있다.

$view = view('greeting')
          ->with('name', 'Edward')
          ->with('age', 20)
          ->withLocation('Jeju'); // 매직 메소드

컨트롤러

규모가 커지면 routes.php 파일 하나로만 로직을 다루는 것보다 컨트롤러를 활용해서 구조화하는 것이 낫다. 컨트롤러로 HTTP 요청을 조작하는 로직을 쉽게 묶을 수 있다. 컨트롤러는 app/Http/Conterollers에 저장한다.

컨트롤러는 App\Http\Conterollers\Controller 기초 클래스를 필수적으로 상속해야 한다. 기본적인 컨트롤러는 다음과 같다.

<?php namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller {

    /**
     * Show the profile for the given user
     *
     * @param  int  $id
     * @return Response
     */
    public function showProfile($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }

}

UserController를 라우터에서 다음과 같이 연결할 수 있다.

$app->get('user/{id}', 'App\Http\Controllers\UserController@showProfile');

의존성 주입

Lumen과 Laravel의 서비스 컨테이너는 타입 힌트를 통해 의존성을 해결해준다. 생성자 주입, 메소드 주입 둘 다 사용 가능하다. 다음 코드는 생성자의 타입 힌트 UserRepository, store 메소드의 타입힌트 Request로 의존성이 주입되는 예제다.

<?php namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use Illuminate\Http\Request;

class UserController extends Controller {

    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Store a new user.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->input('name');
    }

}

미들웨어

HTTP 미들웨어는 HTTP 요청과 응답을 제어할 수 있도록 돕는 구조로 micro-Framework에서는 흔히 사용되고 있다. (Python의 uWSGI, .Net의 OWIN) 미들웨어는 app/Http/Middleware에 위치한다.

미들웨어를 활용하기 위해 구성해야 하는 메소드는 handle이다.

<?php namespace App\Http\Middleware;

class OldMiddleware {

    /**
     * Run the request filter.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('age') < 200) {
            return redirect('home');
        }

        return $next($request);
    }

}

미들웨어로 HTTP 요청과 응답을 모두 제어할 수 있는데 $next($request)를 기준으로 그 앞에서는 요청을 제어하고 뒤에서는 응답을 제어할 수 있다.

다음은 요청을 제어하는 미들웨어로 이 내용을 실행한 후 어플리케이션에 접근하게 된다.

<?php namespace App\Http\Middleware;

class BeforeMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        // Perform action

        return $next($request);
    }
}

다음은 응답을 제어하는 미들웨어 예시로 어플리케이션에서 처리가 끝난 후 클라이언트에게 전달되는 응답을 제어할 수 있다.

<?php namespace App\Http\Middleware;

class AfterMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}

Service Provider와 Service Container

Lumen은 Service ProviderService Container로 기초를 구성하고 있다. Service Provider는 어플리케이션을 시작하기 전에 준비해야 할 작업을 처리할 수 있다. bootstrap/app.php를 열어보면 $app->register() 메소드를 확인할 수 있는데 이 메소드를 통해 추가적인 service provider를 등록할 수 있다.

Service Container는 클래스 간의 의존성을 해결하기 위한 도구로 생성자 또는 “setter” 메소드를 통해 의존성을 주입해준다. $app->bind(), $app->singleton()을 통해 resolver를 등록할 수 있다. 컨테이너에서 의존성을 주입 받기 위해서는 $foobar = $app->make('FooBar'); 방식으로 make 메소드를 사용하는 방법이 있고, 앞서 살펴본 방식인 생성자나 개별 메소드에서 타입 힌팅을 이용해 의존성을 주입할 수 있다. 자세한 내용은 각 문서를 참고하자.


Micro-framework지만 그 말이 무색할 만큼 현대적인 PHP 개발에서 필요한 필수적인 요소는 모두 포함된 강력함을 보여주고 있다. 이 포스트에서 자세하게 다루진 않았지만 많은 서비스도 제공하고 있으며 기존에 laravel을 사용하고 있다면 더 간편하게 사용할 수 있을 것이라 본다. 지금까지 나온 micro-framework 중에서는 가장 마음에 드는 구성이라 차기 프로젝트에서 사용하는 것을 생각해보고 있다. 앞으로 기대가 많이 되는 프레임워크다.

  • PHP 기반의 Micro Frameworks 정리 
  • PHP 5.3에서 새로운 기능으로 네임스페이스가 추가되었다. (= 이미 오래된 기능이다.) 많은 현대 언어는 이미 이 기능을 추가한지 오래지만 PHP는 조금 늦게 추가되었다. 최근에 개발되는 대다수의 PHP 라이브러리는 네임스페이스로 패키징해 composer, League 등을 통해 제공되고 있어 현대 PHP를 사용하려고 한다면 필수적으로 알아야 하는 기능이다.

    PHP에서는 같은 이름을 가진 두 클래스를 동시에 사용할 수 없다. 클래스는 항상 유일해야만 한다. 이 제한으로 인해 서드파티 라이브러리에서 User라는 클래스명을 사용하고 있을 때는 User라는 클래스명을 사용할 수 없었다. 이렇게 간단한 클래스명을 사용하지 못하는건 불편하다.

    PHP 네임스페이스는 위와 같은 클래스명 중복 문제를 해결한다. 그 뿐 아니라 코드를 패키징하거나 벤더명을 지정해 소유권을 표시하는데도 사용할 수 있다.

    전역 네임스페이스

    여기 간단한 클래스가 있다.

    <?php
    class Edward
    {
    
    }
    

    별로 특별한 부분이 없다. 다음과 같이 사용할 수 있다.

    <?php
    
    $edward = new Edward();
    

    이 상황에서 이 클래스는 전역 네임스페이스를 가졌다고 볼 수 있다. 즉 이 클래스는 네임스페이스 없이 존재한다. 그냥 일반 클래스와 같다.

    단순한 네임스페이스

    이제 네임스페이스 밑으로 클래스를 만들어보자.

    <?php
    namespace Haruair;
    
    class Edward
    {
    
    }
    

    위에서 만든 클래스와 유사하지만 작은 차이가 있다. namespace 라는 지시문이 추가되었다. namespace Haruair;는 여기서 작성한 모든 PHP 코드가 Haruair 네임스페이스와 관련이 되어 있음을 뜻하고 이 파일에서 생성한 클래스가 모두 Haruair 네임스페이스에 포함되어 있음을 뜻한다. 네임스페이스를 통해 클래스를 생성하면 다음과 같이 사용할 수 있다.

    <?php
    $edward = new Haurair\Edward();
    

    위 코드와 같이 네임스페이스와 함께 클래스를 선언할 수 있다. 네임스페이스와 클래스 사이에는 백슬래시()로 구분이 된다. 위와 같은 방법으로 네임스페이스로 클래스를 다룰 수 있다.

    이와 같은 방법으로 여러 단계의 위계를 활용하고 있는 경우를 많이 볼 수 있다.

    This\Namespace\And\Class\Combination\Is\Silly\But\Works
    

    의존성 원칙

    PHP는 현재의 namespace에 따라 상대적으로 동작한다.

    <?php
    namespace Haruair;
    
    $edward = new Edward();
    

    Haruair 네임스페이스 내에서 개체를 생성했다. 동일한 네임스페이스에 속해 있는 상황이기 때문에 Haurair\EdwardEdward로 호출해 사용할 수 있다.

    이런 상황에서 반대로 생각해볼 수 있는 부분은 네임스페이스 내부에서 상위 또는 최상위에 있는 네임스페이스나 클래스는 어떻게 접근할 지에 대해서다.

    PHP는 클래스명 앞에 백스래시()를 넣어 전역 클래스 또는 글로벌 네임스페이스를 사용하고 있음을 명시적으로 선언할 수 있다.

    <?php
    $edward = new \Edward();
    

    만약 다른 네임스페이스에 속한 클래스인 Drink\CokeHaruair 네임스페이스 내에서 사용한다면 앞서 예제와 같이 작성할 수 있다.

    <?php
    namespace Haruair;
    
    $coke = new \Drink\Coke();
    

    매번 전체 위계를 입력하는 것이 번거롭다면 use를 활용할 수 있다.

    <?php
    namespace Haruair;
    
    use Drink\Coke;
    
    $coke = new Coke();
    

    use를 활용하면 다른 네임스페이스에 있는 하나의 클래스를 현재 네임스페이스 내에서 사용할 수 있게 해준다. 동일한 클래스명을 불러오게 되는 경우가 온다면 다음과 같이 활용할 수 있다.

    <?php
    namespace Haruair;
    
    use Drink\Pepsi as BlueCoke;
    
    $pepsi = new BlueCoke();
    

    위와 같이 as 키워드를 쓰면 Drink\Pepsi 클래스에 별칭 BlueCoke를 지정해 사용할 수 있다. 같은 이름의 클래스 여럿을 동시에 사용한다 해도 문제 없다.

    <?php
    namespace Facebook;
    
    use Twitter\User as TwitterUser;
    
    class User {}
    
    $twitter_user = new TwitterUser();
    $facebook_user = new User();
    

    Twitter 네임스페이스에 있는 UserTwitterUser 별칭으로 불러오면서 충돌을 회피했다. 이와 같이 충돌을 피하고 의도와 필요에 따라 기능을 모아서 사용할 수 있다.

    use는 필요한 만큼 넣어서 사용할 수 있다.

    <?php
    namespace Haruair;
    
    use Twitter\Follower;
    use Facebook\WallPost;
    use Cyworld\WallPost as CyPost;
    

    구조

    네임스페이스는 단순히 충돌을 피하기 위해서만 사용하는 것이 아니라 조직이나 소유권을 표기하기 위해 사용하기도 한다.

    오픈소스 라이브러리를 만든다고 가정하자. 내가 만든 코드를 다른 사람이 사용한다면 분명 좋을 것이다. 다만 내 코드를 사용하는 사람들에게 불편함을 주지 않았으면 좋겠다. 클래스명이 충돌하게 되면 엄청나게 불편할 것이 확실하다. 그래서 다음과 같이 네임스페이스를 구분하기로 했다.

    Haruair\Blog\Content\Post
    Haruair\Blog\Content\Page
    Haruair\Blog\Tag
    

    여기서 내 아이디를 사용해서 이 코드가 누가 만들었는지 표시하는 것과 동시에 내 라이브러리 안에 만들어 코드를 사용하고자 하는 사람의 코드와 충돌하지 않도록 돕는다. 내 기초 네임스페이스에 여러개의 서브 네임스페이스를 만들어 내부 구조를 잡았다.

    Composer를 사용하면 PSR-0, PSR-4를 통해 정해진 규칙에 따라 네임스페이스를 통해 클래스 정의를 자동으로 불러오는 등의 작업을 할 수 있다. 위 두 문서에서 이 유용한 방식을 확인해보는 것을 강력하게 추천한다.

    제한

    PHP가 제공하는 네임스페이스에는 한계가 있다. 다른 언어들에서의 구현과는 거의 유사하지만 약간 다른 점이 존재한다. Java의 경우, wildcard(*)를 이용해 해당 네임스페이스에 속해 있는 모든 클래스를 한번에 불러올 수 있다. 또한 Java에서의 import는 앞서 살펴 본 use와 같은 역할을 해서 패키지나 네임스페이스 내에 있는 클래스를 쉽게 이용할 수 있게 돕는다. 다음은 Java의 예시다.

    import haruair.blog.*;
    

    위와 같은 코드로 haruair.blog의 모든 패키지를 로드할 수 있다.

    PHP는 이와 같은 방법으로 불러올 수 없다. 대신 상위의 네임스페이스를 use로 불러와 유사하게 사용할 수 있다.

    <?php
    namespace weirdmeetup;
    
    use Haruair\Blog as Cms;
    
    $post = new Cms\Content\Post;
    $page = new Cms\Content\Page;
    $tag = new Cms\Tag;
    

    한 네임스페이스에서 많은 클래스를 동시에 사용할 때 위 방법이 도움이 된다.

    더 읽을 거리

    생산성 도구를 안 쓰는 사람은 있어도 하나만 써보는 사람은 없다는 얘기가 있다. 생산성 도구를 사용하는 사람이라면 거기서 거기인 앱이 계속 나오는 기분이 들겠지만 하나씩 사용해보면 각자가 독특한 개성을 보여주고 있어 자신에게 가장 맞는 도구를 찾기 위해 여럿을 사용하게 된다는 얘기다. 그만큼 생산성 도구는 개인화 경향이 강한 분야다.

    나는 무료로 제공되는 마크다운 에디터를 쓰다가 Ulysses 추천을 받고 Ulysses를 구입해 한동안 사용했다. 강력한 기능을 많이 지원하는 도구지만 md 파일로 바로 저장되는 식이 아니라 자체 라이브러리에서 관리되는 형태에서 불편함을 느꼈고 그 이후로는 만능 에디터 Sublime Text를 사용하고 있었다. Sublime Text를 쓰면서 사실 큰 불편은 없지만 내가 좋아하는 Typewriter 모드가 없었다. 1

    Typed

    Typed는 지난번 9to5mac에서 Screenflow와 Things2 등과 번들을 구입할 때 같이 포함되어 있었다. 이 번들이 Things2를 단일로 구입하는 것보다 저렴하길래 구입했었다. 매년 GTD를 새해 선물처럼 구입하는데 한 달을 못간다. 아무튼 기대하고 구입한 Things2 보다는 Typed를 더 열심히 사용하고 있다. (No strings attached.)

    사운드 트랙 내장

    에디터 리뷰에서 이 얘기를 맨 처음 하는게 웃기긴 한데 에디터에 8가지 사운드 트랙이 내장되어 있다. 들어보면 차분해질 만 한 음악을 끊임 없이 반복적으로 틀어준다. 같은 노래를 오랜 기간 반복적으로 듣는 편이라서 알게 모르게 흥얼거리고 그러다보면 다른 생각에 빠지게 되는데 이 사운드 트랙은 흥얼거릴 거리도 없는 조용한 음악을 반복해서 들려준다. 그러다 보니 심지어 이 에디터를 코딩할 때도 켜놓고 그 음악을 틀어놓기도 한다.

    Typewriter 모드

    Typed도 Typewriter 모드를 지원해서 타자기처럼 글이 작성되는 위치를 중앙에 고정해놓고 사용할 수 있다. 에디터에서 글을 길게 작성하면 입력하는 커서 위치가 화면 가장 밑에 있게 되는데 이걸 방지해준다. 커서 밑에 공간이 없으면 답답한 기분도 들고 계속 글을 작성하는게 어색한 느낌이 드는데 Typewriter 모드로 그 공간을 만들어줄 수 있다.

    사실 이 기능은 대부분의 마크다운 에디터에서 지원하고 있다. 게다가 Ulysses에서는 이 기능에 더 많은 옵션을 지원하고 있는데도 Typed에서의 구현이 더 마음에 든다. 많은 옵션을 제공하는 것 보다 딱 맞는 옵션으로 하나만 지원하는 것은 사용자의 입장에서 더 배려받는 기분을 들게 한다.

    Little big things

    문단/문장 강조 기능도 편리하다. 기본적으로 문장 강조 기능을 사용하고 있는데 현재 커서가 위치한 문장 외에는 옅은 색상으로 변경해준다. 글을 다 작성하고서 문장 또는 문단 단위로 리뷰할 때 편리하다.

    글자, 단어 수 표시도 깔끔하다. 우측 상단에 글자 수, 단어 수 표시가 있는데 눈에 거슬리지 않고 단순하다.


    Typed Main

    생산성 관련 서비스/앱을 개발하는 것이 힘들다는 얘기를 듣게 된 이후 생산성과 관련된 앱을 하나씩 사용하게 될 때마다 작은 부분까지도 유심히 살펴보려고 노력하고 있다. 안쓰는 사람은 안쓰고 쓰는 사람은 여럿을 사용한다는 생산성 앱에서 자신에게 딱 맞는 앱을 찾는다. 반대로 개발하는 입장에서 생각한다면 좁은 마켓에 있는 더 작은 타겟을 대상으로 딱 맞는 앱을 만들어야 하는데 사용자층이 좁을 수록 freemium이 아닌 이상 가격을 산정하기가 쉽지 않을 것 같다.

    그 사용자층을 넓히기 위해서 세세하게 제어 가능한 옵션을 제공할 수도 있겠다. 단순하게 생각하면 세세한 옵션이 사용자에게 편의를 준다고 생각할 수 있지만 반대로 너무 많은 옵션에 혼란이 올 수 있다. 그건 마치 칫솔 회사의 상황과도 비슷하다. 사용자가 칫솔모를 자신의 구강 구조에 맞게 다듬어서 사용하는 킷을 파는게 아니라 사용자에게 미리 최적화 된, 잘 닦일 수 있는 칫솔을 디자인해서 팔아야 하는 것이다. 사용자는 앱을 칫솔처럼 구입해서 사용한다. 본인이 직접 설정을 잘못해놓고는 앱이 불편하다고 여기고 떠날 수도 있다. 사용자에게 “모든 설정을 제공하고 스스로 trial & error을 통해 자신에게 최적의 환경을 만든다” 가정은 유니콘 찾기일 수도 있다. 최고의 설정은 개발사가 정해서 제공해야 한다. 물론 개발사가 타겟한 사용자 층에 대해 전문가가 되는 것은 말처럼 쉽지 않겠지만 말이다.

    앞으로도 나에게 잘 맞는 생산성 앱을 만날 수 있었으면 하는 기대가 있다. 비싸더라도 구입할 수 있도록 열심히 잔고를 늘려놔야겠다.

  • 왠지 플러그인으로 있을 법도 한데 설치하고 코딩, 글쓰기 스위칭 할 때마다 끄고 켜야 한다면 없는게 낫다. 
  • 몰입에 대해 어렵지 않다고 생각하고 지내왔고 실제로도 쉽게 몰입하는 경향이 있어서 처음엔 좋은 주제라고 느꼈는데 막상 작성하려고 하니 최근에는 오랜 시간을 몰입해본 기억이 없었다. 쉽게 몰입했던 그 감각이 다시 살아났으면 하는 마음에서 요 며칠은 퇴근하고 집중적으로 번역 글을 올리는데 시간을 사용했다.

    몰입을 방해하는 환경

    나는 몰입에 큰 어려움이 없는 편이었다. 하지만 최근 들어 흐름을 끊는 방해 요소가 많아졌다. 한두 번 정도야 다음 몰입에 영향을 주지 않지만 이 상황이 반복되면 그 상황에 스트레스를 받는다. 반복적으로 집중이 틀어지면 또 집중이 흐트러질 것이라는 사실을 몸이 무의식적으로 예상하고 있어서 그런지 다시 몰입 상태로 진입하기 힘들다. 그러면 일도 지루하게 느껴지고 재미도 없어진다.

    이렇게 스트레스를 받는 상황에서는 주변 환경에 대해 배로 민감해진다. 누군가의 대화, 문 여닫는 소음, 전화벨 소리, 발소리, 기침 소리, 심지어는 작은 노티피케이션 하나에도 방해로 느껴지고 스트레스를 더 받는 악순환에 빠진다. 작은 자극에도 불편함을 느끼고 있다면 무언가 잘못되고 있음을 자각해야 한다. 공간을 개선하거나, 공간을 탈출하거나. 참으면 본인만 손해다. 그래서 요즘은 몰입하는 것 자체보다 자연스럽게 몰입할 수 있는 환경을 만드는데 고민을 많이 하고 있다.

    몰입의 대상과 조건

    당연한 점인지 몰라도 나는 내가 좋아하는 일을 할 때 몰입을 잘한다. 재밌는 일을 좋아한다. 처음 하거나 아직 익숙지 않아서 서툰 일을 재밌어한다. 숙련도가 늘어나는 반복 작업도 재밌다. 없는 것을 만드는 작업을 재밌다. 이미 있는 것을 개선하는 작업도 재밌다. 내가 단순해서 그런지 웬만하면 다 재미있어한다.

    재미있으려면 호기심이 생겨야 하고 호기심이 있으려면 여유가 있어야 한다. 마음에 여유가 없으면 눈앞에서 치워버리는 것이 목표가 된다. 급박한 기일을 가진 데드라인을 정하고 달리는 것도 좋지만 이런 긴장감이 반복 되다보면 결국 고무줄은 늘어나고 사람은 지치고 여유의 종말을 맞이하게 된다. 쉴 때 확실하게 쉬지 않으면 여유가 생기지 않는다. 반복되는 데드라인에도 지치지 않는 사람은 애초에 내가 생각하는 범위 이상으로 여유의 크기가 남다르게 큰 사람이거나 쉴 때 엄청나게 잘 쉬는 사람이다. 이런 사람들은 내적 요인, 외적 요인 가릴 것 없이 쉽게 몰입해서 과정을 즐긴다.

    내게 몰입에 도움이 되는 공간은 주변은 어둡지만 내 손 닿는 곳은 밝은, 그리고 밀폐된 느낌이 있는 공간이다. 즉 도서관 열람실 같은 분위기에서 쉽게 몰입한다. 오랫동안 집중해서 무엇인가 한 기억은 모두 그와 비슷한 공간이라 그런지 그와 같은 환경에서 몰입이 더 잘된다. 어쩌면 학습된 결과인지도 모르겠다.

    노래를 듣거나 TV를 보면서는 절대 안된다. 가사 없는 곡은 좀 도움이 되는 것 같다. 조용한 공간에서 집중이 잘되는 편이다. 화이트 노이즈 같은 에어컨 돌아가는 소음이나 컴퓨터 돌아가는 소음 있는 공간도 잠만 잘 오고 집중에 방해된다.

    소음 얘기하다 생각났는데 세상엔 두 종류의 사람이 있다고 한다. 방을 정리하는 사람과 방을 정리하지 않는 사람인데 전자는 뭘 해도 바로 정리하는 타입이고 후자는 정리를 아예 하지 않거나 어쩌다 한번 정리하는 타입이다. 나는 후자인 편인데 어쩌면 물건이나 옷이 많아서 소음을 줄여주는 차음재 역할을 하는 건 아닐까 생각이 든다. 1 아무튼 나에겐 번잡스러운 공간도 집중에 도움이 된다.

    몰입 해야 하나

    몰입의 순간에 행복감을 느낀다. 몰입의 대상이 무엇이냐에 따라 상관 없이 몰입이라는 경험 자체에 중독성이 있다. 성취로 인한 기쁨도 있지만 내가 얼마나 집중해서 결과를 만들었는지도 만족의 척도가 된다. 결과는 썩 좋지 않더라도 몰입하고 나면 “괜찮아, 나는 꽤 즐겁게 한 걸” 하고 기분 좋게 넘어갈 수 있다. 힘들 수 있는 과정을 즐겁게 수행하는 비결이 된다.

    반대로 몰입에서 오는 만족감 때문에 몰입에 대한 강박관념에 사로잡히기 쉽다. 일을 잘 끝내고 나서도 몰입하지 못했다는 이유로 허전한 기분에 빠지거나 일을 망쳤다는 생각에 사로잡힐 수 있다. 개운하지 않은 기분. 이것도 은근 스트레스다. 몰입은 필수가 아니다. 과정에서 부가적으로 얻을 수 있는 즐거움인데 몰입 자체에 집착하면 강박증세만 심화된다.

    몰입은 자신의 환경을 돌아봐야 한다는 신호다. 몰입이 안되는 상황을 자신 탓이라 생각하면 그건 해결될 수 없는 방식이다. 사람은 다 다르고 다 다른 방식의 몰입 트리거를 가지고 있을 텐데 다 같은 방법으로 몰입할 수 있을까. 누구는 너저분한 책상에서 집중이 잘 되고 누구는 깨끗한 책상에서 집중이 잘 된다. 몰입하지 못한다고 답답해하기보다 어떤 환경에서 내 여유를 챙기고 충전할 수 있는지 고민하고, 어떤 환경에서 몰입할 수 있는지 찾는 것이 더 중요하다.


    이상한모임에서 진행하는, 다양한 주제로 함께 글을 쓰는 글쓰기 소모임입니다. 함께 하고 싶다면 http://weirdmeetup.herokuapp.com 에서 가입하시고 #weird-writing 채널로 오세요!

  • 방정리 잘 안하는 사람의 흔한 변명. 
  • 운영하는 사이트의 외부 유입을 확인하기 위해 Google Analytics를 기본적으로 설치하는 편이다. Google Analytics는 설치만 해도 유입 트래픽을 보기 좋게 정렬해서 보여주는 편이지만 좀 더 세부적으로 데이터를 구분하기 위해서는 몇가지 추가적인 작업이 필요하다. 웹사이트로의 외부 유입을 추적하는 경우에는 맞춤 캠페인(Custom campaign)을 활용해 필요한 데이터만 분리해 확인할 수 있다. 이 기능이 도움 될 만한 시나리오를 생각해보면 다음과 같다.

    • SNS에서 유입되는 트래픽 중 내가 직접 작성한 포스트로 유입된 트래픽만 확인
    • 광고를 내는데 이 광고로 들어오는 사람이 얼마나 되는지 확인
    • 뉴스레터에서 어느 링크를 클릭해 웹사이트로 유입되는지 확인

    맞춤 캠페인 URL

    맞춤 캠페인에 필요한 것은 맞춤 캠페인 URL이다. 이 URL을 통해 접속하면 URL에 포함된 매개변수를 활용해 분류할 수 있다. 맞춤 캠페인에서는 다음 5개의 매개변수를 활용해서 분류할 수 있다.

    • utm_source: 사이트로 트래픽을 보내고 있는 광고주, 사이트, 출판물 등을 식별 (예: Google, 도시검색, 뉴스레터4, 빌보드)
    • utm_medium: 광고 또는 마케팅 매체 (예: CPC, 배너, 이메일 뉴스레터)
    • utm_campaign: 제품의 개별 캠페인 이름, 슬로건, 프로모션 코드 등
    • utm_term: 유료 검색 키워드. 유료 키워드 캠페인에 대해 직접 태그를 추가할 경우 utm_term을 사용하여 키워드를 지정
    • utm_content: 같은 광고 내에서 유사 콘텐츠 또는 링크를 구분. 예를 들어 하나의 이메일 메시지에 두 가지 클릭 유도문안 링크가 있는 경우 utm_content를 사용하여 각각 다른 값을 설정하면 어떤 버전이 더 효과적인지 확인할 수 있음.

    추후 편리하게 리포트를 확인하려면 일관성을 가지고 입력해야 한다. 예를 들면 대소문자를 섞어쓰지 않도록, 소스(source)와 매체(medium)를 혼동해 뒤바꿔 쓰지 않도록, 시즌별로 반복된다면 연월 등 규칙성이 있는 포맷으로 작성하는게 도움이 된다.

    캠페인 URL 만들기

    이 맞춤 캠페인 URL은 URL 작성도구로 쉽게 만들 수 있다. Google Analytics에서 결과를 확인하기 위해 이 도구로 맞춤형 캠페인 URL 예제를 하나 만든다.

    URL 작성도구

    Google Analytics에서 확인하기

    작성한 주소를 통해 접속을 하면 Google Analytics 에서 해당 정보와 함께 수집된다. Google Analytics는 접속하더라도 정보 수집이 바로 반영되지 않아 디버깅 하기가 힘들었는데 이제는 실시간 접속을 확인할 수 있어서 예전보다 간편하다. 확인을 위해 위 URL 작성도구에서 연습으로 만든 맞춤형 캠페인 URL로 접속해서 창을 열어둔 채 다음 순서대로 Google Analytics에서 확인해보자.

    1. Google Analytics에 접속
    2. 자신의 사이트를 선택한 후
    3. 왼쪽 탭에서 **실시간(Real-Time) > 트래픽 소스(Traffic Source)**를 클릭

    트래픽이 URL 작성도구에서 입력한 데이터와 함께 정상적으로 들어오는 것을 확인할 수 있다.

    실시간 유입 확인

    이렇게 유입된 트래픽은 **획득(Acquisition) > 캠페인(Campaigns) > 모든 캠페인(All Campaigns)**에서 종합된다.

    모든 캠페인

    직접 맞춤설정을 통해 맞춤 보고서를 만들어서 데이터를 확인하는 것도 가능하다. 더 자세한 내용을 확인하고 싶으면 맞춤 캠페인 – 웹로그 분석 도움말을 확인하자.

    msdn 블로그에 게시된 New Features in C# 6포스트를 요약했다. C# 6는 VS 2015 프리뷰와 함께 제공된 버전으로 여러가지 문법 특징이 추가되었다. 이 포스트는 요약이라 내용이 좀 부실할 수 있는데 상세한 내용은 위 포스트를 참고하자.

    표현식

    nameof

    새로운 형태의 문자열로 프로그램 요소의 이름을 간혹 알아내야 할 상황을 마주하는데 그때 사용할 수 있는 표현식이다. 점 표기법(dot notation)을 사용한 경우 가장 마지막 식별자를 반환한다.

    if (x == null) throw new ArgumentNullException(nameof(x));
    WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"
    

    person이 스코프 내에서 타입이 아닌 변수라면 두번째 코드는 허용되지 않는다.

    문자열 인터폴레이션

    번거로웠던 String.Format()을 간편하게 작성할 수 있다.

    var s = String.Format("{0} is {1} year{{s}} old", p.Name, p.Age);
    s = "\{p.Name} is \{p.Age} year{s} old";
    s = "\{p.Name,20} is \{p.Age:D3} year{s} old"; // 형식 지정
    s = "\{p.Name,20} is \{p.Age:D3} year{(p.Age == 1 ? "" : "s")} old"; // 표현식도 쓸 수 있음
    

    Note. 프리뷰 이후 문법이 변경되었다. s = $"{p.Name,20} is {p.Age:D3} year{{s}} old";

    Null 조건부 연산자

    체이닝 등 호출이 연속적으로 이뤄지는 상황에서 null 확인을 더 쉽게 만드는 연산자다.

    int? length = customers?.Length; // customers가 null이면 null 반환
    Customer first = customers?[0]; // customers가 null이면 null 반환
    
    // null 병합 연산자인 ??와 함께 사용. customers가 null 일 때, 값은 0
    int length = customers?Length ?? 0;
    
    // 뒤에 따라오는 멤버 접근, 엘리먼트 접근 등은 customers가 null이 아닐 때만 호출
    int? first = customers?[0].Orders.Count();
    
    // 위와 동일한 표현
    int? first = (customers != null) ? customers[0].Orders.Count() : null;
    
    int? first = customers?[0].Orders?.Count(); // 연속으로 사용 가능
    

    다만 ?을 사용한 직후에는 문법적으로 모호함이 있어서 바로 호출을 할 수 없다. 그래서 바로 대리자를 호출할 경우 다음과 같이 작성해야 한다.

    if(predicate?(e) ?? false) { ... } // 에러 발생
    if(predicate?.Inkobe(e) ?? false) { ... }
    

    이벤트를 작동할 때 다음과 같이 작성할 수 있다.

    PropertyChanged?.Invoke(this, args);
    

    이 문법은 스레드 안정성을 보장하는데 좌측을 평가한 후 값을 임시로 저장하기 때문이다.

    인덱스 이니셜라이저

    개체 이니셜라이저를 확장에 다음과 같이 인덱스를 넣을 수 있게 되었다. 표현식 하나로 JSON 개체를 만들 때 유용하다.

    var numbers = new Dictionary<int, string> {
      [7] = "seven",
      [9] = "nine",
      [13] = "thirteen",
    };
    

    확장 Add 메소드

    확장 Add 메소드를 컬렉션 이니셜라이저에서 사용할 수 있다. 예전엔 인스턴스 메소드만 Add를 호출할 수 있었다.

    오버로드 향상

    오버로드 확인(resolution)이 향상되었다. 상세 내역은 소개되지 않았고 nullable 값 타입을 받을지 말지, 메소드 그룹을 대리자로 받는 등의 향상이 있을 것이라고 한다.

    표현문

    예외 필터

    CLR 호환으로 VB와 F#에만 있던 기능이 이제 추가되었다.

    try { ... }
    catch (MyException e) if (myfilter(e))
    {
      ...
    } // if 문이 참일 때만 `catch` 블럭이 실행된다.
    

    필터는 일반적이고 수용할 수 있는 형태로 “오용”을 할 수 있다. 파생작업을 위해 다음과 같이 사용할 수 있다.

    private static bool Log(Exception e) { /* log it */; return false; }
    ...
    try { ... }
    catch (Exception e) if (Log(e)) {}
    

    catch/finally 블럭에서의 Await

    Resource res = null;
    try
    {
        res = await Resource.OpenAsync( ... ); // 원래 되던 부분
    }
    catch(ResourceException e)
    {
      await Resource.LogAsync(res, e); // 이제 가능한 부분
    }
    finally
    {
      if (res != null) await res.ColseAsync(); // 여기서도 가능
    }
    

    맴버 선언

    자동 프로퍼티 이니셜라이저

    필드에 이니셜라이져 하는 것과 비슷하다. 이 방법으로 이니셜라이징 하면 setter를 거치지 않고 내부 형식 바로 저장이 된다.

    public class Customer
    {
      public string First { get; set; } = "Jane";
      public string Last { get; set; } = "Doe";
    }
    

    Getter only 자동 프로퍼티

    setter 없이 자동 프로퍼티를 사용하는게 허용된다. 이 방식은 readonly로 암묵적인 선언이 된다.

    public class Customer
    {
      public string First { get; } = "Jane";
      public string Last { get; } = "Doe";
    }
    

    위와 같이 초기화 하지 않는 경우에는 다음과 같이 타입 생성자에서 선언하면 값이 내부 형식에 바로 저장된다.

    public class Customer
    {
      public string Name { get; };
      public Customer(string first, string last)
      {
        Name = first + " " + last;
      }
    }
    

    이 문법은 표현 타입을 더 간소하게 만든다. 하지만 변형 가능한 타입과 불변 타입의 차이가 없어진다. 변형 가능한 클래스도 상관이 없다면 자동 프로퍼티를 기본으로 사용하는 것도 좋다. 여기다 getter only 자동 프로퍼티는 변형 가능한 타입과 불변 타입을 더 비슷하게 만든다.

    표현-본문 함수 멤버

    표현-본문 함수 멤버는 메소드와 프로퍼티, 다른 종류의 함수 멤버들의 본문을 블럭이 아닌 람다처럼 표현식을 바로 쓸 수 있도록 지원한다. 실제로 람다처럼 람다 화살표로 작성한다. 블럭 본문에 단일 반환값을 가진 형태와 동일하다.

    public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
    public static Complex operator +(Complex a, Complex b) => a.Add(b);
    public static implicit operator string(Person p) => $"{p.First} {p.Last}";
    

    void를 반환하는 메소드, Task를 반환하는 비동기 메소드에서도 동일하게 사용할 수 있지만, 이 경우에는 람다에서와 같이 꼭 문 표현식(statement expression)이 사용해야 한다.

    public void Print() => Console.WriteLine(First + " " + Last);
    

    프로퍼티와 인덱서도 다음과 같이 쓸 수 있다. get 키워드가 없는 대신 표현식 본문 문법에 따라 암묵적으로 사용된다.

    public string Name => First + " " + Last;
    public Customer this[long id] => store.LookupCustomer(id);
    

    파라미터 없는 구조체 생성자

    파라미터 없는 구조체 생성자가 허용되었다. 다음 구조체에서 new Person()은 선언된 생성자에 따라 기본값을 제공한다. default(Person)로 기본값을 사용하거나 new Person[...] 형태로 배열을 사용하면 해당 생성자는 실행되지 않는다. 명시적으로 구조체 타입과 함께 new 키워드를 사용했을 때만 해당된다.

    struct Person
    {
      public string Name { get; }
      public int Age { get; }
      public person(string name, int age) { Name = name; Age = age; }
      public Person() : this("Jane Doe", 37){ }
    }
    

    임포트

    using static

    using 으로 정적 멤버 타입을 스코프에서 직접 사용할 수 있다. 프리뷰에서는 정적 클래스의 멤버만 불러올 수 있다.

    using System.Console;
    using System.Math;
    
    class Program
    {
      static void Main()
      {
        WriteLine(Sqrt(3*3 + 4*4));
      }
    }
    

    Note. 다음과 같이 디자인이 변경되었다.

    1. using에서 using static으로 변경
    2. 구조체나 enum과 같은 멤버의 비정적 타입을 임포트 할 수 있음
    3. VB 모듈의 멤버나 F#의 최상위 함수를 임포트 할 수 있음
    4. 최상위 스코프에서 확장 메소드는 임포트 할 수 없음. 확장을 해온 원 클래스는 불러오지 않고 확장 메소드만 불러오면 안되기 때문.

    위 모든 기능들은 VS 2015 프리뷰에서 확인할 수 있다.


    이 글을 뒤늦게 확인하고 C#6의 문법적인 변경점을 살펴봤다. (아직 기본적인 문법도 잘 모르긴 하지만.) C# 개발은 얼마 해보지 못했는데 문자열 인터폴레이션만 봐도 편리한 기능들이 많이 나오는구나 느낄 수 있었다. 이번 달 아니면 다음 달 내로 베타가 시작될 것 같은데 기대된다.

    좋은 커밋 메시지 작성하기에서 레퍼런스였던 On commit messages를 번역한 글이다. 이전의 글은 커밋 메시지에 대한 글이긴 했지만 간략한 편이었다. 이 글에서는 어떤 방식으로 커밋을 구성해야 하고 어떻게 커밋을 보내면 안되는지 등 실제적인 수준에서 참고할 만한 이야기가 많았다.


    지난 몇 주 동안 놀랄 만큼 많은, 커밋 메시지에 대한 토론을 읽게 되었다. 그 중에는 개발자와 함께 막 새 프로젝트를 시작하려는 사람들도 많았다. 그래서 그들을 돕기 위해 커밋을 할 때 해야 할 일과 그 일을 왜 해야 하는지에 대한 목록을 작성해봤다. (힌트: 리눅스 커널 메일링 리스트는 이 일을 아주 바람직하게 하고 있다. 그곳에서 배우자.)

    모든 소프트웨어 프로젝트는 협동 프로젝트다. 적어도 프로젝트엔 두 명의 개발자가 일하게 되어 있다. 나 혼자 최초 개발자로 스스로 개발을 한다고 해도 몇 주, 몇 달이 지난 후에 작성한 코드가 무엇인지 생각해내야 하는 미래의 내가 존재한다. 미래의 나는 새로운 버그가 발생하거나 새로운 기능을 추가해야 할 때마다 매번 특정 부분의 코드에 대한 맥락을 다시 파악해야만 한다.1

    코드 조각의 맥락을 다시 파악하는 일은 정말 낭비일 수밖에 없다. 물론 이 일을 완전히 회피할 수는 없겠지만, 이 시간을 최소화할 수 있도록 노력해야 한다. 바로 커밋 메시지가 그 역할을 담당한다. 그렇기 때문에 커밋 메시지를 보면 그 개발자가 좋은 협력자인지 아닌지를 알 수 있게 된다.

    좋은 커밋 메시지는 패치에 관한 다음 세 가지 질문에 답을 할 수 있어야 한다:

    • 왜 이 코드가 필요한가? 코드는 버그 수정, 기능 추가, 성능 향상, 신뢰성, 안정성을 위한 변경일 수 있다. 물론 단순한 오·탈자 교정일 수도 있다.
    • 어떻게 이슈를 해결했는가? 짧아서 명백한 패치의 경우 이 부분을 생략해도 된다. 다만 깊은 수준의 묘사로 어떻게 문제에 접근했는지 나타내야 한다.
    • 패치가 어떤 영향을 만드는가? 명백한 부분을 포함해 벤치마크 결과, 부작용 등을 포함할 수 있다.

    이 세 가지 질문으로 실제 코드 변경에 대한 맥락을 알 수 있게 된다. 또한 리뷰어와 다른 개발자가 그 맥락을 통해 차이점을 보고 적절한 방법을 선택해 문제에 접근했는지 확인하게 된다. 또한 좋은 커밋 메시지는 메인테이너에게 안정 브랜치에 포함해도 괜찮은지, 또는 배포에 포함해도 괜찮은지 결정하는데 도움된다.

    이 세가지 질문에 대한 답이 없는 패치는 대부분 쓸모 없는 패치다. 이런 경우에는 이 패치가 어떤 일을 하고 어떻게 이슈를 해결했는지 직접 찾아야 하기 떄문에 리뷰어에게 부담만 될 뿐이다. 복잡할대로 복잡한 패치에 많은 수의 리뷰어가 필요한 상황. 그 의미는 결국 원 개발자가 좋은 커밋 메시지를 작성하지 않았다는 이유만으로 많은 인력 투입 시간(man-hours)이 낭비된다는 뜻이다. 거기에 만약 메인테이너가 프로젝트의 소스 컨트롤 관리 통제를 철저히 하고 있다면, 개발자가 제출한 패치는 거절 당할 것이고 개발자는 다시 시간을 소비해 패치를 다시 작성해야 하며, 리뷰어는 리뷰에 또 시간을 소비하게 되는, 최악의 경우도 발생할 수 있다. 이처럼 시간 낭비는 빠르게 늘어난다. 단지 몇 분 시간을 투자해서 커밋 메시지를 작성하는 것이 이런 비경제적인 시간 소비를 없앨 수도, 최악의 상황을 만들 수도 있는 것이다.

    오픈소스가 아닌 일반 소프트웨어 회사도 이 내용을 충분히 고려해야 한다. 적당한 소스 컨트롤 관리 규칙이 없으면 결국 비용이 발생한다.

    어떻게 해야 더 잘할 수 있을까

    물론 이상적인 커밋 메시지는 이래야 한다는 엄격한 정의는 없다. 하지만 몇 가지 일반적인 규칙은 있다. 커밋은 정확히 하나의 로직 변경을 포함해야 한다. 로직 변경은 새로운 기능을 추가하거나 특정 버그를 수정하는 것 등을 의미한다. 몇개의 단어로 고수준의 변화를 묘사할 수 없다면 단일 커밋으로는 너무 복잡한 상태인 것이다. 변경은 가능한 한 그 스스로 이해할 수 있도록 간결해야 한다. 많은 패치에서 발생한 에러가 작은 패치에서 발생한 에러보다 낫다. 가장 우선이 되는 규칙으로, 커밋 메시지만 읽어도 다른 개발자가 납득할 만큼 비슷한 시간을 들여 같은 패치를 구현할 수 있어야 한다.

    git을 사용한다면 git add -p (또는 -i)를 활용해 각각의 변경 사항에 따라 로직을 이해할 수 있는 수준의 단일 커밋 단위로 쪼개야 한다.

    Git 커밋 양식

    만약 패치를 git으로 제출한다면, 그 양식은 거의 표준화 되어 있다. 첫 행은 변경에 대한 요약이다. (행의 최대 길이는 프로젝트마다 다르지만 일반적으로 행 당 50자에서 78자 사이다.) 이 첫 줄을 가장 많이 보게 되고 그만큼 중요하다. 많은 git 도구가 이 방식으로 동작하거나 이 양식에 최적화되어 있다. 첫 행 요약 다음으로 빈 행을 입력하고 그 뒤로 필요에 따라 패치에 대한 상세 내역을 여러 문단으로 작성한다. 코드를 설명하지 말고 의도와 접근 방식을 설명한다. 로그는 현재형으로 작성한다.

    로그를 사랑하는 방법을 배울 것

    나는 과거에 CVS를 사용했는데 (SVN도 조금) 이 도구는 정말 사용하기가 쉽지 않았다. 거의 쓸모가 없었는데 도구도 그랬고 사용 가능한 정보도 그랬다. 현재는 코드를 들여다 보는 것 보다 git의 로그를 더 자주 본다. git 로그 도구는 일하고 있는 프로젝트에서 CVS의 로그나 커밋 규칙에 비해 대단히 뛰어나기 때문에 훨씬 편리하다. 코드보다 git 로그를 더 많이 붙잡고 왜 이 코드가 이런 방법으로 작성되었는지 git을 통해 살펴보는데 대부분의 시간을 쓴다. 이 방법은 확실히 많은 시간과 노력을 아끼게 해준다. X 서버 버그에서 가장 짜증나는 점은 코드가 XFree86에서 넘어오는 과정에 git 히스토리가 남아있지 않은 곳에서 나타난다는 점이다. 만약 아직 소스 컨트롤 관리의 로그 도구를 사용하고 있지 않다면 이 도구와 더 친해지길 추천한다.

    하면 안되는 것

    커밋을 할 때 평균적으로 나타나는 몇 가지 일반적인 죄악이 있다. (그렇다. 읔.)

    • 소스 컨트롤 관리는 백업 시스템이 아니다! 개인적으로 정말 싫어하는 유형이다. 개발자 중에는 이 도구를 퇴근용 커밋 즉, 퇴근하기 직전에 변경한 모든 코드를 커밋하는 사람이 있다. 그 결과는 아무짝에 쓸모가 없다. 코드 이곳 저곳에서 확인되는 변경점은 몇개월이 지나면 그 누구도 이해할 수가 없다. 실제 코드 작성자를 포함해서 말이다. (덧붙여: 대학교는 절대 이런 쓰레기 방식으로 가르치지 말 것.)
    • 파일 당 커밋. 파일 하나 이상에서 로직 변경이 실제로 발생하지 않았는데도 커밋하는 경우가 있는데 지나치게 분리해서 커밋하면 안된다.
    • 게으른 커밋 메시지, “여러가지 고치고 정리했음” 또는 이와 비슷하게 작성한 모든 커밋을 의미한다. 이런 경우를 비FOSS 프로젝트에서 종종 본 적이 있었는데 이런 커밋은 결국 당신에게 되돌아와 상처를 입힌다. 알려진 버그인지 알아내는 것은 불가능에 가깝고 문제를 분리하기도 어려울 뿐더러 그 누구도 프로젝트에서 무슨 일이 일어나고 있는지 따라가기 어렵게 만든다.
    • 하나의 패치 안에 두 가지 변경. 예를 들면 “버그 2345를 수정했고 모든 foo를 bar로 수정했음”과 같은 커밋이다. 버그 2345에서 명칭 변경이 요구되고 있다 하더라도 이런 커밋은 여러개의 패치로 분리해야 한다. 이 버그 픽스를 안정 브랜치에 적용해야 하는 상황일 때 다른 내용이 함께 있기 때문에 적용할 수가 없다. 잘못된 패치를 유용한 덩어리에 넣는 일은 누군가 직접 처리하기 전까진 프로젝트에 아무런 가치를 더해주지 못하는 일이라 가장 시간 소모적이고 짜증나는 일 중 하나다.
    • 코드 변경에 공백 변경을 함께 넣는 경우. 사막에서 바늘 찾는건 재미있는 게임이지만 패치를 들여다보고 있을 때는 전혀 아니다. 이 방법은 분명 버그를 소개하는 가장 강력한 방법이긴 하다. 비록 수백 줄의 코드에 들여쓰기를 변경해 모든 코드가 변경된 상태로 표시되고 있으니 말이다. 재미로 한건지 장점이 있어서 한 것인지는 둘째치고 어느 위치에 버그가 있었고 그 버그를 어떻게 수정했는지 거의 아무도 찾을 수 없다.
    • 너무나도 사랑스러운 코드 누락. 새로운 기능을 추가하기 위해 수백 줄 코드 패치를 작성하는 동시에 이 기능을 위해 기존에 있던 인프라 구조를 절반 이상 재작성을 한 경우다. 그 결과로 수백 줄의 코드를 리뷰해야 하고 매번 이 영역에 있던 코드와 관련된 버그를 발견하게 된다.인프라 구조를 한번에 한 조각씩 수정하는 것으로 시작하고 그 작업을 하고서 맨 위에 새로운 기능을 꼽았으면 훨씬 쉽고 적은 시간을 사용했을 것이다. 위와 같은 방법을 자주 사용해서, 즉 빈번하게 코드 덤프를 통채로 적용하는 프로젝트가 있다면 이는 외부 개발자들의 사기를 떨어뜨린다. 당신이라면 코드를 기여하는 프로젝트가 아니라 지독한 잡음에서 신호를 골라내는 일에 시간을 쓰는 프로젝트에 참여하겠는가?
    • 패치와 관계 없는 공백 변경. 리뷰어는 패치에 관한 큰 그림을 생각해야 한다. 공백만 있는 덩어리는 혼란스럽다. 리뷰어는 그 공백이 실제 변경인지 무시해도 되는지 확인하기 위해 더 추가적인 노력을 기울이게 된다. 빈 행이 추가되거나 제거되는 경우는 그렇게 나쁘지 않다. 정말 나쁜 경우는 들여쓰기 변경이다.

    위와 같은 상황에는 수많은 변명도 존재하는데 대부분 다음과 같이 대답하기를 좋아한다. “그래도 동작하잖아요!” 물론 동작은 한다. 하지만 코드는 정적이지 않다. 얼마 간의 시간 내에 코드는 아마 이동하고, 다시 작성될 것이며, 다른 방법으로 호출되거나 버그가 포함되어 있다는 사실이 발견될 수도 있다. 동시에 최초의 개발자는 코드를 움직이고 누구도 왜 그 코드가 그렇게 움직였는지 모를 수도 있다. 최악의 경우는 모든 사람이 코드를 만지는 것을 두려워하는 상황인데 누구도 어떻게 실제로 동작하는지 알 수 없기 때문이다.

    또 다른 일반적인 변명은 다음과 같다. “하지만 나는 이 프로젝트에 참여하는 유일한 사람입니다.” 사실이 아니다. 모든 소프트웨어 프로젝트는 (위에서 보는 것과 같이) 협동 프로젝트다. 누구도 단순히 근시안적으로 생각하지 않는다. 특히 FOSS 프로젝트는 외부의 기여자 즉, 테스터, 개발자, 우선순위를 정하는 사람, 사용자 등에 의존적이다. 그들이 참여하게 만드는 게 어렵다면 그 프로젝트는 더 쉽게 망할 것이다.

    좀 덜 일반적이지만 최근에 자주 보이는 또 다른 변명은 소스 컨트롤 관리가 너무 느리다는 불평이다. 분산 소스 컨트롤 관리는 이 이슈를 해결했기 때문에 시간도 절약하고 돈 절약에도 아마 보탬이 될 것이다.


    1. 최초 개발자라는 표현이 아무리 다듬어도 어색해서 “나”로 대입해서 번역했다. 

    Don’t “Push” Your Pull Requests의 번역글이다. 코드를 기여하기 전에 그 커뮤니티의 분위기를 아는 것, 커뮤니티와 소통하는 것이 얼마나 중요한가에 대한 이야기다.

    Ilya! Thank you for giving me the opportunity to translate this article. 😀


    문제에 직면하거나 불편한 부분을 만나게 되면 에디터를 열고 코드를 수정해 문제를 해결하려고 파고들게 된다. 선한 의도로 간단한 패치를 만든다. 패치를 풀 리퀘스트로 보내고서 의자에 편히 기대고 결과를 지켜보게 된다. 자신을 스스로 대견하다고 생각하면서 말이다. 하지만 간단한 패치라고 해도 본 코드에 합쳐지기 전에 전례에 따라 준수해야 하는 몇 가지 원칙들이 있기 마련이다.

    그 후에도 가끔 우연하게 불편한 문제를 발견하게 된다. 표면적인 문제로 아주 단순하게 해결할 수 있어서 하루 잠깐 짬을 내 개선을 할 수도 있고, 더 깊이 들어가 본질적인 문제를 살펴봐야 할 수도 있다. 그 결과, 이 문제를 완벽하게 해결한 300줄 이상의 패치, 수정된 여러 파일과 함께 지구를 구한 기분으로 풀 리퀘스트 보내게 된다.

    여기에 모든 문제가 있다. 당신은 여기에 투자한 모든 시간과 작업, 노력을 인정받길 원한다. 하지만 메인테이너는 공포에 질려 패치를 들여다보고는 왜 그가 이 패치를 수락할 수 없는지 나열하게 된다. 그는 어디서 시작해야 할지도 몰라서 잘못된 부분만 콕 집어 살펴보게 된다. 당신이 기여한 코드는 어떤 방향으로도 빠르게 진행될 수 없다. 이런 문제는 어느 쪽을 비난할 수도 없는 상황이다.

    풀 리퀘스트를 “떠넘기지” 말 것

    기여는 항상 환영해야 마땅하지만, 깜짝 놀랄 패치는 결국 짐이 될 뿐이다. 당신은 분명 도움을 주기 위해 한 일이다. 하지만 누군가는 당신보다 더 오랜 기간 동안 그 코드를 유지보수 해왔다. 그러므로 그들을 놀라게 하는 것을 피하고 그들의 방식을 먼저 따르자. 더 최악인 경우는, 협소한 문제를 해결하기 위해 지역적인 변경을 만들어 프로젝트에서의 전체적인 구현을 놓치게 되는 경우가 있다. 미리 세워둔 로드맵 계획 또는 전체적인 구조적 결정 등을 엉망으로 만든다. 좋은 아이디어라도 몇 프로젝트에는 적절하지 못한 구현이 될 수 있다. 심지어 이 과정이 다른 사람의 노력을 무의미하게 만든다는 사실을 알아차리지 못할 수도 있다. 게다가 좋지 않은 타이밍이라면 이런 여러 이유로 인해 사람들이 당신을 등질 수도 있다.

    코드 리뷰는 어렵다: 빨리 시작하자

    다음부터는 에디터로 뛰어들기 전에 먼저 토론을 시작하고, 내 문제와 해결책에 대한 개요를 만들어 내가 어떤 일을 하려고 하는지 먼저 알리자. 그 목표를 설명하는 것은 한 문단으로 정리되어야 한다. 만약 그 글이 에세이가 되고 있다면, 문제를 더 잘게 쪼개야 한다는 신호거나 문제가 명쾌하게 정의되지 않은 상황이라는 의미다. 그 변경폭이 크다면 리뷰어를 정해 함께 전체적인 디자인을 살펴야 한다. 초기에 도움을 요청하고 손을 빌리면 만들려는 하는 코드를 마술과도 같이 제 시간에 작성해 “합칠(당겨질)” 수 있다.

    코드 리뷰는 어렵다. 사실, 코드 리뷰의 효율은 200-400줄 코드를 넘으면 극단적으로 떨어지기 때문에 리뷰 속도는 시간당 300-500줄 미만으로 볼 것을 권장하고 있다. 이 방식을 계속 기억하자. 큰 커밋은 작고 다루기 쉬운 단위로 자른다. 가장 우선되는 규칙으로, 30분 정도 짧은 시간 동안만 리뷰를 한다. 또는 200줄 보다 짧은, (똑똑하진 않더라도) 깔끔하고 좋은 코드를 보는 것이다. 여러 개의 커밋이라도 로드맵을 함께 준다면 각각의 조각들에 대한 피드백을 한번에 받을 수 있을 것이다.

    좋은 작업은 “밀어내기”가 아닌 “당기기”를 얻는다

    스레드를 읽는 것만큼 귀찮은 일은 없다. 하지만 그보다 더 나쁜 경우는 기여하는 과정 끝에 내 기여가 잘못된 방향으로 흘러갔다는 사실을 받아들여야 할 때다. 기여자는 코드가 수락되지 않으면 상처를 받는다. 메인테이너는 딜레마에 빠진다. 그들은 커뮤니티를 통해 코드가 만들어지고 굴러가길 원하는데 이런 광팬들의 코드를 깊이 살펴보는 과정에서 그들과 대립하게 된다. 거기엔 승자가 없다. 게다가 어느 쪽도 비난할 수 없다.

    다음엔 에디터로 뛰어들기 전에 토론을 시작하고, 내 문제와 해결책에 대한 윤곽을 잡아보고, 작업을 제안하자. 그저 어둠 속에서 작업해서 풀 리퀘스트로 “떠넘기지” 말자.


    GitHub을 비롯해 이전보다 쉽게 참여할 수 있는 오픈소스 커뮤니티가 많아졌다. 내가 직접 작성한 코드라는 기쁨에 눈이 가려 코드를 꼼꼼히 잘 살펴보지 않고 풀 리퀘스트를 보낸 경험이 있어 이 글을 읽으며 그때의 일이 생각이 났다. 이제 막 오픈소스에 참여하려는 사람들에게 도움이 될 만한 글인 것 같아 번역하게 되었다.

    최근 #이삭줍기, #코드줍기라는 이름으로 사소한 개선점을 오픈소스에서 찾아 pull request를 보내고 있는데 각각의 오픈소스마다 가진 문화나 분위기가 달라 배우는 점이 많다. 코드를 읽는 과정은 단순히 코드와 그 구조를 이해하는 것에 그치지 않고 타인을 이해하고 넓은 시야를 갖는 데 도움이 되는 느낌이다. 앞으로도 꾸준히 참여하고 참여한 커뮤니티에서 feel the room 할 수 있도록 노력해야겠다.

    코드줍기란

    erlang/otp의 위키에서 Writing good commit messages라는 짧은 글을 보고 한국어로 옮겼다. 대부분의 오픈소스는 각각의 Contribute 문서를 통해 어떤 관례를 사용하는지 밝히고 있는 편이지만 대부분 아래의 방식과 크게 다르지 않았다.


    좋은 커밋 메시지는 다음 세가지 중요한 목적이 있다:

    • 리뷰 프로세스를 빠르게 만든다.
    • 좋은 릴리즈 노트를 작성하게 돕는다.
    • 미래의 코드 메인테이너를 돕는다. (그 메인테이너가 당신일 수도 있다!) 어떤 코드 변경이 일어났는지, 왜 이 특정한 기능이 추가되었는지 수년 후에 이야기 해도 알 수 있도록 말이다.

    커밋 메시지의 구조는 다음과 같다. 이 내용은 git scm에서 자세하게 확인할 수 있다:

    (50자 미만의) 변경에 대한 짧은 요약
    
    필요하다면 상세한 설명을 첨가한다. 행 당 72자가 넘지 않도록 유의한다. 맥락에 따라 첫 줄은 이메일의 제목처럼,
    나머지는 본문처럼 다뤄지는 경우가 있다. 요약과 본문을 구분하는 빈 행은 본문을 생략하지 않는 이상 필수적이다.
    rebase와 같은 도구를 사용하면 혼란을 줄 수 있기 때문이다.
    
    추가적인 문단은 빈 행 다음에 작성한다.
    
    - 개조식 서술 또한 괜찮음
    
    - 블릿(bullet)으로 하이픈(-)이나 별표(*)를 사용하고, 한 칸의 공간을 띄고 각 항목 사이 빈 행을 넣는
      방식이 일반적이나 다양한 관례가 있음.
    
    

    해야 할 일

    • 요약과 설명을 작성할 때는 자신이 무엇을 했는지를 명령형으로 작성한다. 즉 다른 사람에게 이 일을 지시하듯 작성한다. “고쳤다 fixed”, “추가했다 added”, “변경했다 changed” 대신 “고치다 fix”, “추가하다 add”, “변경하다 change” 식으로 작성한다.
    • 항상 두번째 빈 행을 추가한다.
    • 커밋 메시지에서 줄 바꿈을 한다. (gitk등 커맨드 환경에서 커밋 메시지를 수평 스크롤 없이 쉽게 읽을 수 있는데 도움이 된다.)

    하지 말아야 할 일

    • 요약 끝에 마침표를 찍지 않는다. 요약은 제목이다. 제목은 마침표로 끝나지 않는다.

    • 커밋을 요약하는데 어려움을 느낀다면 커밋 안에 여러 로직의 변화 또는 버그 수정이 있기 때문일 것이다. 이럴땐 git add -p 명령어를 사용해 여러 커밋으로 분리하는 것이 낫다.

    레퍼런스

    커밋 메시지에 대한 좋은 논의를 다음 블로그 포스트에서 확인할 수 있다.

    SitePoint에 게시된, Bruno Skvorc의 Starting a New PHP Package The Right Way 포스트를 번역한 글이다. PHP는 autoload를 이용한 composer를 비롯 다양한 모듈화 방법이 논의되어 실제로 많이 활용하고 있다. PHP의 최근 동향을 살펴 볼 수 있는 포스트라 생각해 한국어로 옮겼다. 연재는 총 3회 분이며 이 포스트는 1회에 해당한다.

    Bruno and Ophélie at SitePoint! Thanks for giving me the opportunity to translate your great article. 🙂


    시각적 인공지능 기계학습 크롤러인 Diffbot을 이야기 할 때, 이 라이브러리를 사용할 수 있는 많은 프로그래밍 언어를 언급했다. 이 많은 언어를 동시에 보고 있으면 거기엔 미끄러져 흠집이 난, 상한 사과도 있기 마련이다. 그 사과 중 하나인 PHP 라이브러리는 이 연재에 기반해 더 나은 라이브러리를 만들고자 한다.

    이 튜토리얼은 좋은 패키지를 어떻게 작성할 것인가에 중점을 두고 있으며 코드는 실제적이고 실무적일 것일 것이지만 Diffbot 자체에 지나치게 집중하진 않을 것이다. Diffbot의 API는 충분히 단순하고 Guzzle의 인터페이스는 PHP 라이브러리 없이도 사용하기 간편하다. 양질의 PHP 패키지를 개발하는데 어떻게 사용할 것인가에 대한 접근 방식은 당신의 프로젝트에서 어떻게 활용해야 할지 배울 수 있다. Diffbot이 패키지의 주제로 선택된 이유는 실제적인 예제로서 입증하는 것이지 단순히 다른 “홍길동” 패키지를 만드는 것을 의미하지 않는다.

    좋은 패키지 디자인

    최근 몇 년 동안, PHP 패키지 디자인을 위한 좋은 표준들이 많이 나왔다. Composer, Packagist, The league 그리고 가장 최근에 The Checklist까지 말이다. 다음 나오는 항목들을 준수한다면 The League를 제외하고는 패키지를 제출할 수 있다. (물론 여기서 작성하는 패키지는 제출하지 않기 바란다. – 우린 단지 서드 파티 API 제공자를 위해 만들었고 또한 아주 제한적인 컨텍스트만 제공하기 때문이다.) 우리가 따를 규칙은 다음과 같다:

    1. 저작권을 포함할 것
    2. 오픈소스여야 할 것
    3. 개발 관련 부분을 배포본과 분리할 것
    4. PSR-4 autoloading 을 사용할 것
    5. Composer 설치를 위해 Packagist에서 호스팅 할 것
    6. 프레임워크에 상관 없이 사용 가능할 것
    7. PSR-2 코딩 표준을 준수할 것
    8. 깊이 있는 주석을 포함할 것
    9. 유의적 버전을 사용할 것
    10. CI와 유닛 테스트를 사용할 것

    이 항목들에 대해 더 자세히 알고 싶다면 다음 문서를 참고한다.

    시작하기

    여기서 Homestead Improved 환경을 다시 사용할 것인데 이는 가장 빠르게 통일된 환경에서 개발할 수 있도록 도와준다. 참고로 다음과 같은 가상 환경을 통해 이 튜토리얼을 진행할 예정이다:

    sites:
      - map: test.app
        to: /home/vagrant/Code/diffbot_lib
      - map: test2.app
        to: /home/vagrant/Code/diffbot_test
    

    이렇게 VM을 통해 hacking을 해보자.

    바닥부터 시작하기 위해 League Skeleton을 사용한다. League의 규칙이 적용된 템플릿 패키지로 쉽게 시작하는데 도움이 된다. 원래 리포지터리에서 fork한 이 리포지터리는 원래 템플릿보다 개선된 .gitignore가 첨부되어 있고 몇가지 소소한 수정이 포함되어 있다. 만약 원하지 않는다면 원본을 사용하고 차이점은 직접 비교해보기 바란다.

    git clone https://github.com/Swader/php_package_skeleton diffbot_lib
    

    이제 composer.json을 다음과 같이 수정할 차례다:

    {
      "name": "swader/diffbot_client",
      "description": "A PHP wrapper for using Diffbot's API",
      "keywords": [
        "diffbot", "api", "wrapper", "client"
      ],
      "homepage": "https://github.com/swader/diffbot_client",
      "license": "MIT",
      "authors": [
        {
          "name": "Bruno Skvorc",
          "email": "bruno@skvorc.me",
          "homepage": "http://bitfalls.com",
          "role": "Developer"
        }
      ],
      "require": {
        "php" : ">=5.5.0"
      },
      "require-dev": {
        "phpunit/phpunit" : "4.*"
      },
      "autoload": {
          "psr-4": {
              "Swader\\Diffbot\\": "src"
          }
      },
      "autoload-dev": {
          "psr-4": {
              "Swader\\Diffbot\\Test\\": "tests"
          }
      },
      "extra": {
          "branch-alias": {
              "dev-master": "1.0-dev"
          }
      }
    }
    

    우리는 일반적인 메타 데이터를 설정하고 요구 항목을 정의했고 PSR-4 autoloading을 설정했다. 이 과정이 위 목록에서 1-6번 항목에 해당한다. 여기서 Diffbot API를 호출할 수 있도록 돕는 HTTP 클라이언트 라이브러리 Guzzle을 요구 항목에 추가한다.

    "require": {
      "php" : ">=5.5.0",
      "guzzlehttp/guzzle": "~5.0"
    },
    

    composer install을 실행하면 모든 의존성을 내려 받는다. 테스트를 위해 포함한 PHPUnit도 포함이 되는데 이 모든 것이 동작하는지 확인하기 위해 다음과 같이 src/SkeletonClass.php를 변경할 수 있다:

    <?php
    namespace Swader\Diffbot;
    class SkeletonClass
    {
      /**
       * Create a new Skeleton Instance
       */
      public function __consturct()
      {
      }
    
      /**
       * Friendly welcome
       *
       * @param string $phrase Phrase to return
       * @return string Returns the phrase passed in
       */
      public function echoPhrase($phrase)
      {
        return $phrase;
      }
    }
    

    그리고 프로젝트 최상위 경로에 index.php도 다음과 같이 추가한다:

    <?php
    require_once "vender/autoload.php";
    $instance = new \Swader\Diffbot\SkeletonClass();
    echo $instance->echoPhrase("It's working");
    

    이제 브라우저로 test.app:8000에 접속해보면 “It’s working” 메시지를 볼 수 있다.

    아직 public 디렉토리나 이와 같은 파일을 만들지 않았다고 걱정하지 말자. 패키지를 만들 때에는 중요하지 않다. 라이브러리를 만들 때에는 패키지에만 집중해야 하고 또한 프레임워크나 MVC가 아닌 패키지만 있어야 한다. 그리고 index.php를 잠깐 잠깐 테스트 용도로 사용하겠지만 대부분 PHPUnit을 통해 라이브러리를 개발할 것이다. 여기서는 index.php를 실수로 리포지터리에 보내는 일이 없도록 .gitignoreindex.php를 추가하도록 한다.

    PSR-2

    현대적인 표준을 따르기 위해 PSR-2를 준수해야 한다. PhpStorm을 사용한다면 엄청 쉬운 일이다. 내장되어 있는 PSR1/PSR2 표준을 선택하거나 CodeSniffer 플러그인을 설치해 PhpStorm 인스팩션으로 활성화시켜 사용할 수도 있다. 아쉽게도 예전 방법을 활용해야 하는데 PHPStorm이 아직 phpcs의 원격 실행을 지원하지 않기 때문이다. (Vagrant VM도 결국 원격 환경이므로. 만약 이 기능을 PHPStorm에 탑재하기 원한다면 여기에서 투표할 수 있다.)

    어찌 되었든 CodeSniffer가 평소처럼 프로젝트에 필요하기 때문에 composer를 통해 설치하고 VM의 커맨드라인에서 구동하자:

    composer global require "squizlabs/php_codesniffer=*"
    phpcs src/
    # 코드를 검사해 리포트를 보여준다
    

    계획하기

    지금까지 만든 틀로 본격적인 개발을 시작할 수 있다. 이제 필요한 부분을 생각해보자.

    시작점

    Diffbot API를 어떻게 사용하든지 사용자는 API 클라이언트의 인스턴스를 생성하길 원할 것이다. 이런 경우 미리 만들어진 API를 호출하는 것 이외에는 할 수 있는 것이 없다. 각각의 API를 사용해서 요청을 쿼리 파라미터로 보내려면 폼에서 ?token=xxxxxx와 같이 개발자 토큰이 필요하다. 단일 사용자는 하나의 토큰만 사용할 것이기 때문에 새 API 클라이언트 인스턴스를 생성할 때 토큰을 생성자에 넣어 생성해야 한다. 물론 모든 인스턴스를 생성할 때 활용하기 위해 전역 토큰으로 생성해 사용할 수도 있다. 위에서 이야기 한 두 가지 방법을 다음과 같이 표현 할 수 있다:

    $token = xxxx;
    
    // approach 1
    $api1 = new Diffbot($token);
    $api2 = new Diffbot($token);
    
    // approach 2
    Diffbot::setToken($token);
    $api1 = new Diffbot();
    $api2 = new Diffbot();
    

    전자는 단일 API 클라이언트 인스턴스를 생성할 때 또는 여러 토큰을 사용할 때(예를 들면, 토큰 하나는 Crawlbot에 다른 하나는 일반적인 API를 사용할 때) 도움이 된다. 후자는 자신의 어플리케이션이 여러 API 엔드포인트에서 여러 차례 사용될 때 매번 토큰을 주입하는게 번거로울 때 활용할 수 있다.

    위 내용을 생각하며 다음과 같이 첫 클래스를 작성할 수 있다. src/Diffbot.php를 작성한다.

    <?php 
    namespace Swader\Diffbot;
    use Swader\Diffbot\Exceptions\DiffbotException;
    
    /**
     * Class Diffbot
     *
     * The main class for API consumption
     *
     * @package Swader\Diffbot
     */
    class Diffbot
    {
      /** @var string The API access token */
      private static $token = null;
    
      /** @var string The instance token, settable once per new instance */
      private $instanceToken;
    
      /**
       * @param string|null $token The API access token, as obtained on diffbot.com/dev
       * @throws DiffbotException When no token is provided
       */
      public function __consturct($token = null)
      {
        if ($token === null) {
          if (self::$token === null) {
            $msg = 'No token provided, and none is globally set. ';
            $msg .= 'Use Diffbot::setToken, or instantiate the Diffbot class with a $token parameter.';
            throw new DiffbotException($msg);
          }
        } else {
          self::validateToken($token);
          $this->instanceToken = $token;
        }
      }
    
      /**
       * Sets the token for all future new instances
       * @param $token string The API access token, as obtained on diffbot.com/dev
       * @return void
       */
      public static function setToken($token)
      {
        self::validateToken($token);
        self::$token = $token;
      }
    
      private static function validateToken($token)
      {
        if (!is_string($token)) {
          throw new \InvalidArgumentException('Token is not a string.');
        }
        if (strlen($token) < 4) {
          throw new \InvalidArgumentException('Token "' . $token . '" is too short, and thus invalid.');
        }
        return true;
      }
    }
    ?>
    

    메소드에서 DiffbotException을 참조한다. src/exceptions/DiffbotException.php를 다음 내용으로 작성한다.

    <?php 
    namespace Swader\Diffbot\Exceptions;
    
    /**
     * Class DiffbotException
     * @package Swader\Diffbot\Exceptions
     */
    class DiffbotException extends \Exception
    {
    
    }
    ?>
    

    Diffbot 클래스에 대해 간단하게 설명하면, token 정적 프로퍼티는 새 인스턴스를 생성하면서 토큰을 넣지 않았을 때 기본값으로 사용한다. 인스턴스를 생성하면서 토큰을 넣으면 instanceToken 프로퍼티로 저장한다.

    생성자에서 토큰을 전달 받는지 확인한다. 만약 받지 않았다면, 미리 선언된 기본 토큰이 있는지 확인하고 만약 없다면 DiffbotException 예외를 발생하게 된다. 이 예외를 위해 위 예외 코드를 작성했다. 만약 토큰이 괜찮다면 인스턴스에 토큰이 설정된다. 반면 토큰이 전달 되었다면 instanceToken으로 저장된다. 위 두 경우 모두 validateToken 정적 메소드를 통해 검증 절차를 거치게 된다. 토큰의 길이가 3글자 이상인지 체크하는, 단순한 프라이빗 메소드로 통과하지 못하면 아규먼트 검증 실패 예외를 발생하게 된다.

    마지막으로 setToken 정적 메소드로 앞에서 말한 전역 토큰으로 활용한다. 이 역시 토큰 검증 과정을 거친다.

    Diffbot 토큰을 보면 API를 호출하는 상황에서 기존에 있던 token을 변경이 가능한데 이렇게 되면 기존에 존재하는 Diffbot 인스턴스는 의도와 다르게 동작할 수 있다. 이런 경우를 대비해 토큰을 인스턴스 생성할 때마다 주입을 하거나 전역적으로 주입하고 Diffbot을 사용하거나 해야 한다. 물론 토큰을 전역적으로 설정하면 인스턴스의 이 설정을 덮어 쓸 수 있다. 물론 전역 토큰도 수정 가능하기 때문에 여러가지 환경에 따라 인스턴스의 토큰이 변경되도록, 이미 존재하는 인스턴스에는 영향을 주진 않을 것이다.

    이 모든 내용이 블럭 주석으로 문서화 된 것을 볼 수 있는데 과하게 문서화 할 필요는 없지만 모두가 이해할 수 있도록 간단하게 작성을 해야 한다.

    결론

    이 파트에서 몇가지 간단한 기능과 환경설정이 들어 있는 스켈레톤 프로젝트로 PHP 패키지 개발을 시작했다. 파트 1에서의 결과는 다음 링크에서 받을 수 있다. 파트 2는 테스트와 실질적인 기능들을 작성하고 기초적인 TDD를 할 예정이다.

    색상을 바꿔요

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

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