이상한모임에서 진행할 사이드 프로젝트에 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에서 그 내용을 볼 수 있다.

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

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

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

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

비너스 베이에서
비너스 베이에서. 이렇게 장대한 백사장은 처음!

2015년 12월 중순부터 2016년 1월 26일까지 한달 조금 넘는 시간동안 가족들과 시간을 보냈다. 아버지는 일이 있으셔서 아쉽게도 함께 하지 못했지만 어머니와 동생들과 함께 멜번과 멜번 근교를 여행하며 좋은 시간을 보냈다. 휴가는 전체 기간 중 반절 정도 냈고 휴가가 아닌 기간엔 시내 구경을 하거나 버스투어로 근교 지역 여행을 다녀왔다.

  • 아이반호 크리스마스 데코레이션 구경
  • 크리스마스 시내 구경, 시청 프로젝션 구경
  • 새해 불꽃놀이
  • IKEA
  • 빅토리아 주립 도서관
  • St Kilda 바닷가
  • 이안 포터 아트센터
  • NGV 상설전시, 특별전시(Ai Weiwei)
  • 보타닉 가든
  • Shrine of Remembrance
  • 빅토리아 마켓
  • 토키 에어비엔비
  • 질롱 시내 (양 박물관, 질롱 갤러리 / 도서관)
  • 웨레비 동물원
  • 호주 테니스 오픈 키즈데이
  • 마운틴 단데농
  • 비너스 베이
  • 모닝턴 페닌슐라
  • 필립아일랜드 투어(헤리티지 농장, 코알라센터, 남극체험 센터, 펭귄 퍼레이드)
  • 저스틴님댁 BBQ
  • 체드스톤

차를 렌트해서 돌아다닌 기간에는 주로 외각 지역을 다녀왔고 그 외에는 대중교통을 주로 사용했다. 집에서 인근 마켓까지 걸어서 다닐 정도 거리가 돼서 가족끼리 걸어 장보러도 자주 다녀왔다. 다녀 온 모든 곳이 좋았고 기억에 남는다.

공항에 마중 간 날부터 배웅하고 온 그 시간까지 함께 보내고 이런 저런 이야기도 많이 했다. 아직까지 함께 지내던 기억이 더 많아서 그런지 오히려 더 자연스럽게 느껴졌고 편한 기분이 들었다. 배웅하고서 집에 돌아가는 길엔 다시 일상으로 돌아가는구나, 회사 일은 어떻게 해야 할까 이런 잡다한 고민이 많이 들었지만 집 문에 들어서는 순간, 동생이 말했던 것처럼 참 허전했다.

해외서 지내며 가장 어려운 일은 관계다. 물론 여기서도 새로운 관계를 알아가고 친해지고 같이 밥도 먹긴 하지만. 오랜 시간 함께한 친구가 힘들 때 기껏해야 문자 몇 자로 위로 해주는 정도고 가족은 힘든 일이 있더라도 “잘 지낸다”고만 얘기한다. 이런 시간은 빚이 되고 어떤 의미로든 갚아야만 할 것 같은 기분이 든다. 이런 부담감이 호주에서의 삶을 더 집중하고 부지런히 살아야겠다는 다짐으로 자연스레 이어진다.

한달 푹 쉬고 지냈고 올해 계획도 아직 정리하지 못했는데 벌써 해야 할 일도 여럿 생겼다. 남은 1월은 차분하게 계획을 세워 올해를 제대로 시작해야겠다.

상속에 관한 포스트를 읽다가 레퍼런스로 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은 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로, 올해는 또 어떤 새로운 기능이 나올지 기대도 된다. 한편으로는 다른 브라우저도 좀 더 사용해보고 둘러봐야겠다는 생각도 든다.

색상을 바꿔요

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

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