tag: 개발 이야기

VS Code에서 TypeScript 환경 꾸리기

2016년 2월 2일

Visual Studio Code에서 TypeScript을 사용하는 환경을 꾸리는 방법을 정리했다. vscode에 아직 기능이 많은 편은 아니지만 여러 편의 기능이 있어 환경을 구축하는데 활용했다. 물론 실무에서 사용할 땐 webpack이나 여타 task 관리도구를 통해 더 쉽게 사용할 수 있다.

여기서 작성하게 될 파일 구조는 이렇다.

.vscode
  tasks.json
dist
  app.js
  app.js.map
src
  HelloWorld.ts
typings
  tsd.d.ts
  angularjs/
  jquery/
  ... tsd로 설치한 애들
tsconfig.json -- 컴파일 설정
tsd.json -- tsd로 설치한 인터페이스 정보

먼저 typescript와 tsd를 설치한다.

npm install typescript tsd -g

tsd로 초기화하고 필요한 라이브러리의 인터페이스를 받는다. 물론 인터페이스만 받는 것이니 실제 라이브러리는 npm이나 bower로 받아서 따로 번들해야 한다.

tsd init
tsd install angularjs/angular --save

tsd init 하면 tsd.json 파일이 생성되고 설치된 패키지의 메타 정보가 저장된다.

tsconfig.json 파일을 추가하고 다음 내용으로 저장한다.

{
    "compilerOptions": {
        "target": "ES5",
        "module": "commonjs",
        "sourceMap": true,
        "rootDir": "src",
        "out": "dist/app.js"
    }
}

추가적인 내용이 필요하다면 문서를 참고하거나 vscode의 인텔리센스를 활용해서 내용을 넣을 수 있다. 그냥 공부용이라면 그냥 다음과 같이 추가하는 것만으로도 기본값을 사용할 수 있어 정신건강에 도움이 된다.

{}

tsconfig.json에 대해서는 TypeScript에서 없이 쓰기 포스트에서 다룬 적이 있다.

vscode에서 해당 디렉토리를 불러온 후 Shift + Cmd + P로 팔레트를 연 후, Tasks: Configure Task Runner를 실행한다. 그러면 .vscode/tasks.json이라는 파일이 생성되는데 vscode에서 task를 바로 실행할 수 있도록 도와주는 설정 파일이다. tsc를 사용해서 typescript를 컴파일하는 것 외에도 gulp나 webpack 등을 호출할 수 있도록 정리가 되어 있어 필요에 따라 주석을 제거하고 사용하면 된다.

tsconfig.json에서 컴파일 할 설정을 이미 다 설정했기 때문에 args를 다음처럼 고쳐준다.

    // args is the HelloWorld program to compile.
    "args": [],

이제 src/HelloWorld.ts를 작성한다.

class Startup {
    public static main(): number {
        console.log("Hello World");
        return 0;
    }
}

저장한 후, Shift + Cmd + B 또는 팔레트에서 Run Build Task를 하면 설정한 것에 따라서 dist/app.jsdist/app.js.map이 생성되는 것을 확인할 수 있다.

vscode의 build task로 gulp를 사용하고 싶다면 적당하게 gulpfile.js를 작성하고 tasks.json에서 taskName을 변경해주면 된다.

gulp는 이렇게 설치해주고,

npm install gulp -g
npm install gulp gulp-typescript --save-dev

gulpfile.js는 이렇게 작성한다. 앞서 작성한 tsconfig.json을 그대로 활용할 수 있다. 만약 다르게 처리하고 싶다면 gulp-typescript 문서를 참조한다.

var gulp = require('gulp');
var ts = require('gulp-typescript');

var tsProject = ts.createProject('tsconfig.json');

gulp.task('scripts', function () {
    var tsResult = tsProject.src()
        .pipe(ts(tsProject));

    return tsResult.js.pipe(gulp.dest('release'));
});

이제 .vscode/tasks.json을 열어 tsc를 주석 처리하고 gulp를 찾아 주석을 해제한 후, 위 gulpfiles.js의 task 이름에 맞게 taskName을 scripts로 변경한다. 저장 후 vscode의 Run Build Task를 실행하면 gulp로 빌드하는 것을 확인할 수 있다.

추가로 gulp에서 watch를 사용하고 싶다면 다음과 같이 task를 작성하면 된다.

gulp.task('watch', ['scripts'], function() {
    gulp.watch(tsProject.config.compilerOptions.rootDir + '/**/*.ts', ['scripts']);
})

실제로 TypeScript와 Angular1.x 환경을 구축할 때는 generator-gulp-angular 같은 제네레이터를 활용하면 깔끔하게 환경을 구축할 수 있다.

중고 사이트 게시물 모니터링 도구 작성 후기

주말의 게으름을 코딩으로 승화, gumtree 모니터링 코드 작성기

2016년 2월 1일

호주에서도 중고나라 같은 gumtree.com.au라는 웹사이트가 있다. 출퇴근을 자전거로 해보고 싶어서 저렴한 자전거를 찾고 있는데 괜찮은 딜은 검트리에 올라오는 족족 팔리기 때문에 수시로 모니터링 하지 않는 한 저렴한 물건을 구하기가 쉽지 않다.

주말 아침에 일어나서 검트리 페이지를 새로고침 하는 내 모습이 처량해서 이 작업을 자동화 하는 코드를 작성하게 되었다.

  • 지역, 키워드로 검색 페이지를 긁어온다
  • 각 판매글을 적절하게 파싱한다
  • 이전에 긁어온 글과 비교해서 새 글을 뽑는다
  • 새 글이 있으면 알림을 보낸다
  • 다음 비교를 위해 저장한다
  • 스케줄로 반복한다

요즘 파이썬을 계속 보고 있지만 내 서버 인스턴스에 이미 설정이 있는 node로 작성하기로 결정했다. 데이터는 그리 크지 않고 단순히 비교용으로 사용하기 때문에 json 파일로 저장하기로 했다. 알림은 메일로 보낼까 하다가 이전부터 익히 들어온 텔레그램 API를 활용하기로 했다.

어떤 라이브러리를 사용할지 찾아봤다.

  • cherrio node에서 사용할 수 있는 jQuery 구현
  • lodash 데이터 조작을 위한 유틸리티
  • request-promise 이름대로 request에 promise를 끼얹은 라이브러리

텔레그램 API도 node-telegram-bot-api 같은 라이브러리가 있는데 작성할 때는 별 생각이 없어서 주소를 문서에서 가져다가 직접 호출했다. 추후에는 이 라이브러리로 변경해야겠다.

스케쥴은 간단하게 crontab에서 5분 간격으로 호출하게 했다. 매 5분마다 페이지를 받아 JSON으로 파싱하고 비교한다.

텔레그램은 몇 안되지만 html 태그를 포함해 메시지를 보내는 것을 지원한다. 그래서 이미지와 링크를 포함해서 다음 이미지처럼 메시지가 온다.

봇 이름은 홈랜드에 나오는 캐리찡

코드를 다 작성하고 느낀 점은,

  • 텔레그램은 처음 사용해봤는데 개발에 활용하기 정말 좋게 문서도 잘 정리되어 있고 기능도 깔끔했음. 나중에 또 기회를 만들어 더 사용해볼 것.
  • 패키지처럼 만드는데 익숙하지 않아서 코드가 많이 너저분. 깔끔하게 만드는 방법을 찾아보고 정리.
  • 파일 입출력에는 동기적으로 동작하는 함수를 사용. 아직 파일 입출력을 비동기적으로 처리하는 것이 익숙하지 않은데 살펴보고 정리할 것.
  • 판매글 비교하기 위한 lodash 코드를 작성하는데 문서를 꽤 오래 봐야 했었음. 문서를 좀 더 보고 유용한 함수를 정리.

게으름 부려서 작성한 코드가 날 공부하게 한다. 얼마나 좋은 게으름인가! 부지런히 찾아보고 정리해야겠다. 코드는 github에서 확인할 수 있다.

Django Girls 튜토리얼 정리

DjangoGirls에서 제공하는 django로 블로그 만들기 요약

2016년 1월 30일

이상한모임에서 진행할 사이드 프로젝트에 Django를 사용하게 되었는데 제대로 살펴본 경험이 없어서 그런지 영 익숙해지질 않았다. 이전에 Django Girls 튜토리얼 – django로 블로그 만들기 포스트를 본 것이 생각나서 살펴보다가 튜토리얼까지 보게 되었다.

이 포스트는 장고걸스 서울에서 번역된 장고 걸스 튜토리얼을 따라 진행하며 내가 필요한 부분만 정리했기 때문에 빠진 내용이 많다. 튜토리얼은 더 자세하게 세세한 부분까지 설명이 되어 있으니 만약 Django를 학습하려 한다면 꼭 튜토리얼을 살펴볼 것을 추천한다.


Django 및 환경 설정하기

$ python3 -m venv myvenv
$ source myvenv/bin/activate
$ pip install django==1.8

프로젝트 시작하기

django-admin startproject mysite .

생성된 프로젝트 구조는 다음과 같다.

djangogirls
├───manage.py # 사이트 관리 도구
└───mysite
        settings.py  # 웹사이트 설정
        urls.py      # 라우팅, `urlresolver`를 위한 패턴 목록
        wsgi.py
        __init__.py

기본 설정하기

settings.py를 열어서 TIME_ZONE을 수정한다.

TIME_ZONE = 'Asia/Seoul'

정적 파일 경로를 추가하기 위해 파일 끝에 다음 내용을 추가한다.

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

데이터베이스는 기본값으로 sqlite3이 설정되어 있다. 데이터베이스 생성은 manage.py를 활용한다.

$ python manage.py migrate

서버를 실행해서 확인한다.

$ python manage.py runserver 0:8000

어플리케이션 생성하기

$ python manage.py startapp blog

mysite/settings.py를 열어 INSTALLED_APPS에 방금 생성한 어플리케이션을 추가한다.

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
)

블로그 글 모델 만들기

from django.db import models
from django.utils import timezone


class Post(models.Model):
    author = models.ForeignKey('auth.User')
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(
            default=timezone.now)
    published_date = models.DateTimeField(
            blank=True, null=True)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title

여기서 사용할 수 있는 필드는 모델 필드 레퍼런스를 참조한다.

생성한 모델을 사용하기 위해 마이그레이션 파일을 생성하고 마이그레이션을 수행한다.

$ python manage.py makemigrations blog
$ python manage.py migrate blog

Django 관리자 사용하기

새로 추가한 모델을 관리자 패널에서 접근하기 위해서 blog/admin.py에 다음 코드를 추가한다.

from django.contrib import admin
from .models import Post

admin.site.register(Post)

관리자 패널에 로그인하기 위한 아이디를 생성한다. 프롬프트에 따라 생성한 후 웹서버를 실행해서 관리자 패널(http://127.0.0.1:8000/admin/)에 접속한다.

$ python manage.py createsuperuser
$ python manage.py runserver 0:8000

테스트를 위해 Posts에 글을 추가한다.

Git 설정하기

리포지터리를 초기화하고 커밋한다. 튜토리얼은 GitHub 사용하는 방법이 설명되어 있다. .gitignore은 아래처럼 작성할 수 있다.

*.pyc
__pycache__
myvenv
db.sqlite3
.DS_Store

PythonAnywhere 설정하기

PythonAnywhere는 Python을 올려 사용할 수 있는 PaaS 서비스로 개발에 필요한 다양한 서비스를 제공한다.

먼저 서비스에 가입해서 콘솔에 접속한다. GitHub에 올린 코드를 clone한 다음 가상환경을 설치하고 진행한다.

$ git clone https://github.com/<your-github-username>/my-first-blog.git
$ cd my-first-blog
$ virtualenv --python=python3.4 myvenv
$ source myvenv/bin/activate
$ pip install django whitenoise

whitenoise는 정적 파일을 CDN처럼 사용할 수 있도록 돕는 패키지다. 먼저 django의 관리자 도구로 모든 패키지에 포함된 정적 파일을 수집한다.

$ python manage.py collectstatic

데이터베이스와 관리자를 생성한다.

$ python manage.py migrate
$ python manage.py createsuperuser

이제 PythonAnywhere 대시보드에서 web app을 추가하고 manual configuration을 python3.4로 설정한다. 가상환경 경로는 앞서 생성했던 /home/<your-username>/my-first-blog/myvenv/로 입력한다.

WSGI 프로토콜도 사용할 수 있는데 Web > Code 섹션을 보면 WSGI 설정 파일 경로가 있다. 경로를 클릭해서 다음처럼 내용을 변경한다.

import os
import sys

path = '/home/<your-username>/my-first-blog'
if path not in sys.path:
    sys.path.append(path)

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

from django.core.wsgi import get_wsgi_application
from whitenoise.django import DjangoWhiteNoise
application = DjangoWhiteNoise(get_wsgi_application())

URL 작성하기

django는 URL을 위해 정규표현식을 사용한다. 앞서 생성한 blog를 mysite로 다음처럼 불러온다.

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'', include('blog.urls')),
]

blog/urls.py를 추가하고 다음 내용을 추가한다.

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.post_list, name='post_list'),
]

View 작성하기

blog/views.py를 열고 post_list를 생성한다.

from django.shortcuts import render

def post_list(request):
    return render(request, 'blog/post_list.html', {})

템플릿 작성하기

blog/templates/blog 밑에 post_list.html을 생성한다. 디렉토리 구조에 유의한다.

Django ORM과 QuerySets

Django에서의 모델 객체 목록을 QuerySets이라 하며 데이터를 정렬하거나 처리할 때 사용한다.

콘솔에서 다음 명령어로 쉘에 접근한다.

$ python manage.py shell

모델은 먼저 불러온 다음에 쿼리셋을 사용할 수 있다.

>>> from blog.models import Post
>>> Post.objects.all()
[<Post: Hello World>, <Post: Koala>]

>>> from django.contrib.auth.models import User
>>> User.objects.all()
[<User: edward>]
>>> me = User.objects.get(username='edward')

>>> Post.objects.create(author=me, title='Goodbye my friend', text='bye')
<Post: Goodbye my friend>
>>> Post.objects.all()
[<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>]

쿼리셋을 다음 방법으로 필터링 할 수 있다.

>>> Post.objects.filter(author=me)
[<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>]
>>> Post.objects.filter(title__contains='Koala')
[<Post: Koala>]

>>> from django.utils import timezone
>>> Post.objects.filter(published_date__lte=timezone.now())
[]
>>> post = Post.objects.get(title__contains="Goodbye")
>>> post.publish()
>>> Post.objects.filter(published_date__lte=timezone.now())
[<Post: Goodbye my friend>]

정렬도 가능하며 체이닝으로 한번에 호출하는 것도 가능하다.

>>> Post.objects.order_by('created_date')
[<Post: Hello World>, <Post: Koala>, <Post: Goodbye my friend>]
>>> Post.objects.order_by('-created_date')
[<Post: Goodbye my friend>, <Post: Koala>, <Post: Hello World>]
>>> Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')

템플릿에서 동적 데이터 활용하기

blog/views.py를 수정한다.

from django.shortcuts import render
from django.utils import timezone
from .models import Post


def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'post': posts})

blog/templates/blog/post_list.html을 수정한다.

<div>
    <h1><a href="/">Django Girls Blog</a></h1>
</div>

{% for post in posts %}
    <div>
        <p>published: {{ post.published_date }}</p>
        <h1><a href="">{{ post.title }}</a></h1>
        <p>{{ post.text|linebreaks }}</p>
    </div>
{% endfor %}

CSS 추가하기

정적 파일은 blog/static 폴더에 넣는다. blog/static/css/blog.css를 작성한다.

h1 a {
    color: #FCA205;
}

템플릿에 적용한다.

{% load staticfiles %}
<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>Django Girls Blog</title>
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
  <div>
      <h1><a href="/">Django Girls Blog</a></h1>
  </div>

  {% for post in posts %}
      <div>
          <p>published: {{ post.published_date }}</p>
          <h1><a href="">{{ post.title }}</a></h1>
          <p>{{ post.text|linebreaks }}</p>
      </div>
  {% endfor %}
</body>
</html>

템플릿 확장하기

base.html을 다음과 같이 작성한다.

{% load staticfiles %}
<!doctype html>
<html lang="ko">
<head>
    <meta charset="utf-8">
    <title>Django Girls Blog</title>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
    <div class="page-header">
        <h1><a href="/">Django Girls Blog</a></h1>
    </div>
    <div class="content container">
        <div class="row">
            <div class="col-md-8">
            {% block content %}
            {% endblock %}
            </div>
        </div>
    </div>
</body>
</html>

이제 post_list.html에서는 위 base 파일을 불러오고 content 블럭 안에 내용을 넣을 수 있다.

{% extends 'blog/base.html' %}

{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                {{ post.published_date }}
            </div>
            <h1><a href="">{{ post.title }}</a></h1>
            <p>{{ post.text|linebreaks }}</p>
        </div>
    {% endfor %}
{% endblock content %}

post detail 페이지 만들기

이제 블로그 포스트를 볼 수 있는 페이지를 만든다. blog/templates/blog/post_list.html의 링크를 수정한다.

<h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>

blog/urls.py에 post_detail을 추가한다. 뒤에 입력하는 내용을 모두 pk 변수에 저장한다는 의미다. pk는 primary key를 뜻한다.

from django.conf.urls import url
from . import views

urlpatterns = [
  url(r'^$', views.post_list, name='post_list'),
  url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail, name='post_detail'),
]

blog/views.py에 post_detail을 추가한다. 만약 키가 존재하지 않는다면 404 Not Found 페이지로 넘겨야 하는데 get_object_or_404 함수를 사용할 수 있다.

from django.shortcuts import render, get_object_or_404

# ...

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

이제 blog/templates/blog/post_detail.html을 생성한다.

{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaks }}</p>
    </div>
{% endblock %}

모델 폼 사용하기

Django는 간단하게 폼을 생성할 수 있는 기능을 제공한다. blog/forms.py를 생성한다.

from django import forms

from .models import Post


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('title', 'text',)

blog/templates/blog/base.html에 이 폼에 접근하기 위한 링크를 추가한다.

<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>

blog/urls.py에 규칙을 추가한다.

    url(r'^post/new/$', views.post_new, name='post_new'),

blog/views.py에 form과 post_new 뷰를 추가한다.

from django.shortcuts import redirect
from .forms import PostForm

#...

def post_new(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('blog.views.post_detail', pk=post.pk)
    else:
        form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})

blog/templates/blog/post_edit.html을 추가한다.

{% extends 'blog/base.html' %}

{% block content %}
    <h1>New post</h1>
    <form method="POST" class="post-form">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="save btn btn-default">Save</button>
    </form>
{% endblock %}

수정 페이지 추가하기

blog/templates/blog/post_detail.html에 다음 링크를 추가한다.

<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>

blog/urls.py에 다음 코드를 추가한다.

    url(r'^post/(?P<pk>[0-9]+)/edit/$', views.post_edit, name='post_edit'),

blog/views.py에 post_edit 뷰를 추가한다.

def post_edit(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('blog.views.post_detail', pk=pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})

PostForm()에서 instance를 추가하는 것으로 내용을 미리 초기화한다.

보안

템플릿에서 사용자 권한이 있는 경우만 확인할 수 있도록 작성할 수 있다. blog/templates/blog/base.html에서 새 포스트 링크를 다음과 같이 변경한다.

{% if user.is_authenticated %}
    <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
{% endif %}

이 튜토리얼 뒤엔 보안을 강화하고 새로운 기능을 추가하는 등의 심화편이 있다. Django Girls Tutorial: Extensions에서 그 내용을 볼 수 있다.

Nginx 설정으로 robots.txt 덮어쓰기

2016년 1월 28일

대부분 개발은 폐쇄망에서 개발하거나 공개되어도 auth 등을 걸어둬 아무나 접속하지 못하는 환경이기 때문에 큰 문제가 없다. 하지만 가끔 크롤링 되지 말아야 할 사이트가 검색엔진에 크롤링 되는 경우가 종종 있다. robots.txt을 .gitignore에 넣어 각 환경에 맞게 파일을 분리해서 사용하는 경우도 있는데 제대로 설정이 되지 않아서 크롤링이 되는 경우도 있다. (누가 뭘 한 지는 모르겠지만.)

이럴 때 nginx에 다음 설정을 추가하는 것으로 robots.txt 파일의 유무와 상관 없이 disallow 규칙을 반환하게 할 수 있다.

location /robots.txt {
    return 200 "User-agent: *\nDisallow: /";
}

사소한 팁이긴 하지만 아직도 호스팅 환경을 FTP로 클라이언트와 공유하게 되는 경우가 많아 이런 문제가 종종 발생한다. (클라이언트의 엄마친구아들이 좀 안다고 들어와서 만져놓고 우리한텐 안만졌는데 고장났다고 하거나) 이렇게 서버 레벨에서 제어하는 것이 유용할 때가 있다.

표현 문제 (Expression problem)

2016년 1월 26일

상속에 관한 포스트를 읽다가 레퍼런스로 c2의 Expression Problem 페이지를 보게 되었는데 내용이 좋아 짧게 번역했다. 원문은 wiki로 작성되어 있으므로 자세한 내용이 궁금하다면 해당 페이지를 참고하자.


“표현 문제(Expression problem)”는 객체지향 프로그래밍과 함수형 프로그래밍 모두에서 정확하게 설명하기 어려운 쌍대문제(dual problem)다.

기본 문제는 간단한 예제로 설명할 수 있다. 사각형과 원을 포함한 모양을 표현하는 것과 그 크기를 구하는 것을 원한다.

함수형 프로그래밍에서는 다음 같은 데이터 타입으로 묘사할 수 있다.

type Shape = Squre of side
           | Circle of radius

그리고 크기를 구하는 area 함수를 다음처럼 하나 작성할 수 있다:

define area = fun x -> case x of
  Squre of side => (side * side)
| Circle of radius => (3.14 * radius * radius)

객체지향 프로그래밍에서는 다음과 같이 작성할 수 있다.

class Shape <: Object
  virtual fun area : () -> double

class Square <: Shape
  side : double
  area() = side * side

class Circle <: Shape
  radius : double
  area() = 3.14 * radius * radius

“표현 문제” 선언은 위와 같은 개체 또는 함수를 ‘확장’하려 할 때 발생한다.

  • 삼각형을 위해 triangle 모양을 추가하면,
    • 객체지향 프로그래밍의 접근 방식이 간편 (새 클래스를 추가하는 것으로 단순하게 해결)
    • 함수형 프로그래밍에서는 어려움 (area를 포함해 Shape를 받는 모든 함수를 수정해야 함)
  • 반면, 둘레를 측정하는 perimeter 함수를 추가할 때,
    • 함수형 프로그래밍에서는 쉬움 (새 함수 perimeter를 추가하면 끝)
    • 객체지향 프로그래밍에서는 어려움 (인터페이스가 변경되면 모든 클래스에 perimeter()를 작성해야 함)

이것이 표현 문제의 핵심이다. 표현 문제는 일반적으로 횡단 관심(cross-cutting concerns, 쉽게 사용하기 위해 모듈을 분리했을 때 그 모듈로 작성하기 어려운 요구사항이 발생하는 것)이라는 큰 문제 집합에서의 특정적인 예시에 해당한다. (여기서 횡단 관심은 여러 “모양의 집합”과 “모양의 기능”에서 발생한다.) 많은 언어에는 이런 표현 문제를 해결하기 위한 디자인을 포함한다. 열린 함수(새로운 패턴 매치를 추가할 수 있는 함수), 열린 데이터 타입(새로운 패턴으로 확장 가능한 데이터 타입), 멀티 메소드(‘열린’ 클래스에서 ‘열린’ 특징을 갖는 다형적 함수), 서술 호출(Predicate dispatch), 그 외에도 이 문제를 해결하기 위한 많은 접근 방식이 있다.

더 일반적인 해결책으로 관심사의 분리(Separation of concerns)도 적용 가능하다.

내가 Google Chrome을 떠나지 못하는 이유

Chrome 예찬을 쓰려고 하지 않았지만 자랑만 줄줄줄 적게 된, 진행형 Chrome 사용기

2016년 1월 5일

Google Chrome은 2008년 말에 퍼블릭 베타로 처음 맛을 본 직후 군입대를 했다. 군자원(?)으로 사용한 것까지 치면 Chrome을 주 브라우저로 사용한지 벌써 6년이란 시간이 흘렀다. 개발에서 사용하는 도구 중 IE6 이후로 가장 오래 사용한 것 같다. (IE6는 7+년 사용했다. 암흑의 시간.) Chrome을 사용하는 기간동안 Webkit이 Blink로 전환되거나 표준과 관련된 갈등 등 여러 이슈가 있긴 했지만 최종 사용자 입장에서 큰 차이 없이 지속적으로 사용하고 있다. 물론 정치적 지지 차원공감해서 Mozilla Firefox를 사용하려고 했던 기간도 있었고, Safari로 전환해보려는 시도도 있었지만 번번이 실패하고 다시 Chrome으로 회귀하고 있다.

편리하고 익숙한 Chrome이지만 좋은 컴퓨터에서도 무겁게 느껴질 때도 있는데, 그럴 때마다 다른 브라우저에 대한 경험도 쌓고픈 생각이 자연스럽게 따라오게 된다. 하지만 간단하지 않은 이유 중 가장 큰 부분은 개발자도구다. 이전에도 개발자도구와 관련된 글을 썼었지만, 매번 새롭게 업데이트 된 기능은 항상 똑똑함에 감탄하고 화려함에 감동까지 밀려온다.

예전에 비해 메모리 문제는 개선되고 있긴 하지만, 부지런히 작업하고 있던 Chrome이 뜬금 없이 멈출 때가 있다. 지금 그렇게 굳어버려서 풀리는 것 기다리다가 떠나지 못하는 이유에 대해 한번 정리해보는 것도 좋지 않을까 싶어 떠나지 못하는 이유를 적어본다. 물론 Chrome에만 있는 기능이 아닐 가능성이 높다.

탭 복원 기능

Chrome에서 탭 복원 즉, 닫은 탭을 다시 여는 기능은 정말 강력한데, 종종 크래시로 탭을 날렸다는 이야기를 들으면 의아할 때가 있다. 탭을 잘못 닫았다면 Cmd + Shift + T를 누르면 된다. 창이 아예 닫히더라도 창이 다시 열리면서 그 창에 있던 모든 탭이 복원된다. 이건 크래시가 났어도 동작한다. Safari나 Firefox에도 있는 기능이거나 확장을 설치하면 되긴 하지만 Chrome처럼 크래시로 닫힌 창에 있는 탭까지 열어주는 기능을 찾지 못했다.

다중 프로파일 지원

Google Apps를 회사에서 사용한다면 개인 계정과 회사 계정을 분리된 세션에서 사용할 수 있다. 예전엔 이 기능이 없어서 시크릿(incognito) 모드를 활용했어야 했는데 각각의 계정을 Chrome에 등록하면 마치 별개의 브라우저를 운용하는 것처럼 사용할 수 있다. 브라우저 히스토리 등 모든 브라우저 기능이 각 계정에 맞춰 동작해서 편리하다.

개발자 도구

개발자 도구는 모든 브라우저가 각각 특색이 있어서 좋다 나쁘다를 얘기하긴 어려운 부분이다. 내가 유용하게 사용하는 부분은 다음과 같다. 다른 브라우저에서도 지원하는 기능이지만 Chrome이 익숙해서 그런지 Chrome에서의 동작 방식과 다르면 불편하게 느껴진다. 예전에 정리한 팁과 달라지거나 추가된 부분만 간단히 정리하면 아래와 같다.

  • 개발자 도구 위에 있는 탭 위치도 움직일 수 있게 개선됨 (드래그 하면 위치 변경)
  • Elements 패널에서 엘리먼트를 드래그 앤 드롭으로 위치 및 순서 변경할 수 있음
  • HEX, RGBA로 전환 가능한 컬러 픽커 지원, 값도 확인하기 편함
  • 각 엘리먼트에 대해서 Break on… 으로 하위 노드 또는 어트리뷰트 변경 추적 가능
  • JavaScript Console에서 Cmd + K로 버퍼 즉, 콘솔 내용을 지워줌 (쉘에서 그 기능처럼)
  • 가끔 JavaScript에서 break point를 걸고 싶을 때가 있는데 debugger;를 코드에 추가하면 그 위치에서 break되고 해당 코드를 확인할 수 있음
  • break 되었을 때 console에서 현재 스코프에 따라 값을 확인할 수 있음
  • console에서 객체값에 대해 오른쪽 클릭하면 global variable에 저장할 수 있음
  • 페이지 내에 있는 다른 frame의 console도 열 수 있음 (가끔 iframe에 써먹기 좋음)
  • Sources 탭에서 format 버튼으로 간단하게 minify한 코드도 정렬해서 볼 수 있음
  • Geolocation API 테스트를 위한 에뮬레이터를 내장
  • transition에 대해 animation 미리보기 지원
  • Network 탭에서 쉽게 스로트링 테스트 가능

생각 나는 것만 적었는데 목록이 길어지고 있다. 잔잔하고 편리한 기능이 많다. Chrome의 프로파일링 기능도 엄청 강력한데 이건 따로 정리해볼 생각이다.

다른 브라우저 개발자 도구에서 지원하는 기능 중에 탐나는 기능은 Firefox에서 XHR을 수정해서 전송할 수 있는 기능인데 크롬에서는 XHR을 재전송 하는 것은 가능하지만 XHR의 header를 수정하는 기능 등은 제공하지 않아 조금 아쉽다. (대신 Postman과 같은 도구를 활용하고 있다.)

이런 이유로 Chrome을 쉽게 떠나지 못하고 있다. 한명의 Chrome fanboy로, 올해는 또 어떤 새로운 기능이 나올지 기대도 된다. 한편으로는 다른 브라우저도 좀 더 사용해보고 둘러봐야겠다는 생각도 든다.