이맥스(Emacs) 패키지 관리

이맥스 설정

개요

이맥스가 실행되면 이맥스는 초기화 파일로부터 Lisp 프로그램들을 로드합니다. 초기화 파일은 아래 세 가지 중에 하나를 선택해서 사용하면 됩니다.
~/.emacs, ~/.emacs.el, or ~/.emacs.d/init.el

패키지

이맥스 24 이후부터 패키지 설치는 거의 ELPA를 사용합니다. 기존에 직접 패키지를 다운받아서 설치하던 방식에서 패키지 매니저를 통해 패키지들을 간편하게 설치 할 수 있게 된거죠.

여기서 ELPA는 이맥스 패키지들이 올라가있는 저장소입니다. 따로 설정을 해주지 않아도 이맥스 버전 24부터는 기본으로 ELPA 패키지 저장소가 설정되어있습니다.

패키지 저장소는 ELPA외에 다른 것들도 있는데 보통 MELPA를 많이 사용합니다. 저장소를 추가로 세팅하는 방법은 아래와 같습니다.

1
2
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

패키지 설치

M-x list-package 명령어를 입력하면 아래와 같이 패키지 목록을 볼 수 있습니다. 여기에 있는 패키지 목록이 바로 ELPA에 등록된 패키지입니다. 만약 MELPA와 같은 저장소를 위와 같은 방법으로 추가했다면 해당 저장소에 있는 패키지도 함께 보여집니다.

list of packages

패키지 로딩

load-path 변수에는 이맥스가 설치된 패키지를 로드할 때 패키지를 검색하는 디렉토리 목록이 저장됩니다. M-x describe-variable 명령어를 통해 load-path 변수의 값을 볼 수 있습니다.

사용하려고 하는 패키지가 load-path에 포함되어 있지 않으면 로딩을 실패합니다.

패키지 커스터마이징

패키지를 설치 했으면 이제 내 손맛에 맞게 커스터마이징을 합니다. 커스터마이징이라 하면 단축키, 자동 로딩 등이 있겠죠. 자동 로딩은 설치한 패키지가 이맥스가 시작 될 때 자동으로 로딩되도록 해주는 것입니다. 설치한 패키지가 필요할 때 마다 매번 패키지를 로드하는건 너무 불편하겠죠? :)

유용한 함수

함수 단축키 설명
describe-function - elisp 함수의 설명을 보여줌
describe-key - 현재 맵핑된 단축키에 대한 설명을 보여줌
describe-mode - 현재 모드의 키바인딩 목록을 볼 수 있음
describe-variable - elisp 변수에 대한 설명을 보여줌

단축키

이맥스 커맨드는 Lisp 함수입니다. 보통 소문자와 하이픈으로된 이름을 가집니다. 이맥스는 이런 커맨드를 다양한 단축키로 제공합니다. 단축키가 잘 정의되어 있고 이를 잘 사용하면 엄청난 생산성을 가질 수 있습니다. 그리고 이는 곧 사람들이 이맥스를 사용하는 가장 큰 이유가됩니다.

따라서 단축키를 익히고 커스터마이징 하는건 이맥스 사용자에게 가장 중요한 일중에 하나입니다.

키맵(Keymaps)

이맥스의 단축키는 키맵을 통해 제공됩니다. 키맵은 전역 키맵(Global keymap) XXX 키맵으로 구성됩니다. 전역 키맵은 이맥스의 기본 모드(Fundamental mode)의 키맵을 정의합니다.

이맥스의 단축키는 키(keys)와 문자(characters)로 구성됩니다. 여기서 키는 컨트롤(Ctrl), 메타(Alt or Meta), 펑션키(, , …)를 말하고 문자는 알파벳과 특수문자들을 생각하면 됩니다.

또한 이맥스에는 메이저 모드와 마이너 모드가 있는데요. 각 모드는 자신만의 키맵을 정의해서 사용할 수 있습니다.

키맵 커스터마이징

글로벌 키맵(Global Keymap)

M-x global-set-key를 이용해 글로벌 키맵의 키를 재정의 할 수 있습니다.

1
(global-set-key [f9] 'gud-break)

로컬 키맵(Local Keymaps)

미니버퍼 맵

마크다운 모드

링크 : https://jblevins.org/projects/markdown-mode/
메뉴얼 : https://leanpub.com/markdown-mode

외부 모듈로 Markdown.pl, MultiMarkdown, Pandoc 중 하나를 설치해야한다.

프리뷰 모드

마크다운 문서를 웹브라우저를 통해 바로 보려면 markdown-preview 모드를 사용하면 된다. 다만 마크다운 문서는 기본적으로 html로 변환을 거쳐야 하기 때문에 이 작업을 해줄 프로그램이 필요하다. 마크다운 모드에서는 Markdown.pl, MultiMarkdown, Pandoc 중 하나를 사용한다고 한다.

Debian 계열의 리눅스에서는 MultiMarkdown을 아래와 같이 설치 할 수 있다.

sudo apt-get install libtext-multimarkdown-perl

모드

이맥스는 다양한 편집 모드를 가집니다. 모드는 메이저 모드와 마이너 모드로 나눠집니다.

메이저 모드(Major mode)

메이저 모드는 C 소스파일과 같이 특정한 파일 타입을 편집할 때나 쉘 버퍼와 같은 특정 타입의 버퍼에 대해 동작합니다. 메이저 모드는 한번에 하나의 모드만 동작할 수 있습니다.

모든 버퍼는 하나의 메이저 모드를 가집니다. 현재 메이저 모드는 아래 그림과 같이 이맥스 창 하단에서 확인 할 수 있습니다.

mode line

가장 기본적인 최소한의 메이저 모드는 펀드멘털 모드로 불립니다. 이 모드는 특정 모드에 한정된 재정의나 변수 설정을 갖지 않기 때문에 가장 기본적인 이맥스의 상태로 동작합니다.

대부분의 메이저 모드는 세개의 메이저 그룹으로 나눌 수 있습니다. 첫 번째 그룹은 플레인 텍스트나 마크업 텍스트를 가집니다. 여기에는 텍스트 모드, HTML 모드, SGML 모드, TeX 모드, 아웃라인 모드가 있습니다. 두 번째 그룹은 프로그래밍 언어들을 포함합니다. Lisp, C, Fortran 등등의 모드를 갖습니다. 세 번째 그룹은 파일의 형식과 관련이 없는 모드들을 갖습니다. Dired, Message, Shell 모드 등입니다.

메이저 모드는 보통 이맥스에 의해 파일이나 버퍼가 생성될 때 자동으로 설정됩니다. 모드를 바꾸고 싶을때는 M-x 뒤에 -mode를 붙여서 변경할 수 있습니다. (ex> `M-x lisp-mode). 모든 버퍼는 하나의 메이저 모드를 갖기 때문에 메이저모드를 다른걸로 바꿀 수는 있어도 끌 수는 없습니다.

각 버퍼의 로컬 변수인 major-mode는 메이저 모드의 커맨드를 이름으로 갖습니다. 예를 들어 M-x lisp-mode를 통해 Lisp 메이저 모드를 선택했다면 major-mode 변수에는 list-mode가 자동으로 설정됩니다.

major-mode의 기본 값은 정해진 메이저 모드가 없는 버퍼에 대해 설정되는 모드입니다. 보퐁 이 기본 값은 fundamental-mode입니다. 이 기본 값을 변경하고 싶으면 아래와 같이 하면 됩니다.

1
(setq-default major-mode `text-mode)

만약 major-mode의 기본값이 nil이면 메이저 모드는 이전 버퍼의 모드를 따라갑니다.

특정 메이저 모드는 보통 모드에 맞게 특정 키, 사용자 옵션, 변수 등을 변경합니다. 예를 들면 보통 프로그래밍 언어 모드에서는 을 해당 언어의 가이드에 맞추어 들여쓰기 해주도록 합니다. 그리고 버퍼 로컬 변수인 comment-start를 수정하기도 합니다. 이 변수는 언어에 따른 주석의 형태를 정의합니다.

*현재 설정된 메이저 모드에 대한 문서를 보려면 M-x describe-mode를 사용합니다. 모드에 대한 자세한 설명과 키 바인딩을 모두 확인할 수 있습니다.

기본 모드를 제외한 모든 메이저 모드는 모드가 활성화 될 때마다 실행되는 커스터마이징이 가능한 List 함수의 목록을 모드 훅(mode hook)으로 정의합니다.

아래는 c++-mode의 훅입니다. 훅의 이름은 모드명-hook입니다. 또한 모든 텍스트 기반의 메이저 모드는 text-mode-hook을, 많은 수의 프로그래밍 언어 모드는 prog-mode-hook을 각각의 메이저 모드가 갖는 훅을 실행 시키기 전에 실행합니다.

1
2
3
(add-hook 'c++-mode-hook
'(lambda ()
(c-set-style "linux")))

메이저 모드의 훅은 마이너 모드의 훅들을 활성화 시키기 위해 자주 사용됩니다. 아래의 코드는 text-mode에 진입할 때 flyspell-mode 마이너 모드를 활성화 시키고 있습니다.

1
(add-hook 'text-mode-hook 'flyspell-mode)

마이너 모드(Minor mode)

마이너 모드는 내가 원할때 켜고 끌 수 있습니다. 또한 특정한 타입의 파일이나 버퍼에 구분없이 동작 할 수 있습니다. 마이너 모드는 다른 메이저 모드나 마이너 모드에 대해 독립적입니다.

버퍼 로컬 마이너 모드는 활성화 된 버퍼에서만 동작합니다. 반면 글로벌 마이너 모드는 모든 버퍼에서 동작합니다.

자동완성

설치 : https://github.com/auto-complete/auto-complete
메뉴얼 : https://github.com/auto-complete/auto-complete/blob/master/doc/manual.md

참고자료

EmacsWiki

이맥스 영문 위키 페이지입니다. 이맥스 사용자에겐 여기가 보물 창고!

안드로이드 ENUM과 Typedef 어노테이션(Annotation)

이 글은 ENUM 사용 시 주의할 점과 어노테이션을 통해 이를 보완할 수 있는 방법에 대해 정리한 글입니다.

주로 참고한 자료는 유튜브 perfmatter 시리즈 중 The price of ENUMs (100 Days of Google Dev) 영상과 Improve Code Inspection with Annotations 문서입니다.

안드로이드 앱 실행 시 메모리 할당

ENUM 사용 시 주의점을 알기 전에 먼저 안드로이드 앱 실행 시 메모리가 어떻게 할당되는지 간단히 짚고 넘어가겠습니다.

앱을 실행시키면 안드로이드는 시스템 메모리 곁에 앱을 위한 메모리를 할당합니다. 이렇게 할당된 메모리는 앱의 코드와 실행중에 동적으로 할당하는 메모리를 위해 사용됩니다. 여기서 앱의 코드는 안드로이드 앱을 빌드할 때 생성되는 DEX 파일입니다.

아래는 The price of ENUMs 영상에서 캡쳐한 이미지입니다.

Android Memory Allocation

ENUM 사용 시 주의점

ENUM은 앱의 코드(DEX) 크기와 런타임 메모리 사용량을 증가시킵니다.

앱의 DEX 크기가 증가된다는건 APK 파일도 커지고 앱이 실행됐을 때 사용하는 메모리의 양도 그만큼 늘어난다는걸 의미합니다. 또한 ENUM은 Integer나 String에 비해 더 많은 메모리를 런타임에 사용합니다.

ENUM은 얼마나 DEX 크기를 더 증가 시킬까?

The price of ENUMs (100 Days of Google Dev) 영상을 보면 그 차이를 분명히 알 수 있습니다.

먼저 상수를 아래와 같이 Integer로 선언한 경우, DEX의 크기는 124 bytes가 늘어납니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final int VALUE1 = 1;
public static final int VALUE2 = 2;
public static final int VALUE3 = 3;

int func(int value) {
switch(value) {
case VALUE1:
return -1;
case VALUE2:
return -2;
case VALUE3:
return -3;
}
return 0;
}

반면에 이를 ENUM으로 선언하면 DEX의 크기는 1,632 bytes가 증가합니다. 무려 13배나 차이가 납니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static enum Value {
VALUE1,
VALUE2,
VALUE3
}

int func(Value value) {
switch(value) {
case VALUE1:
return -1;
case VALUE2:
return -2;
case VALUE3:
return -3;
}
return 0;
}

여기서 바로 ENUM을 사용할 때 왜 주의해야 하는지를 알 수 있습니다. ENUM 사용으로 인한 DEX의 크기가 커지면 앱과 시스템이 사용하는 메모리는 줄어들게 됩니다.

ENUM을 사용함으로써 증가하는 메모리의 양은 어찌보면 그리 크지 않다 여길 수 있습니다. 하지만 ENUM을 무심코 사용하다보면 그 크기는 결코 무시할 수 없게 됩니다.

해결방법 첫 번째, Proguard 사용

프로가드를 사용하면 빌드 시 enum을 Integer로 바꾸는 최적화를 수행해 줍니다.

해결방법 두 번째, Typedef 어노테이션 사용

위의 예제처럼 ENUM의 대안으로 간단히 Integer를 사용함으로써 위의 이슈를 피해갈 수 있습니다. 하지만 ENUM이 괜히 있는게 아닙니다. ENUM을 사용하면 컴파일 및 런타임에 타입 및 값을 체크할 수 있습니다.

API의 인자나 반환 값에 제약을 줌으로써 API가 오동작을 일으킬 여지를 최대한 줄이는 것은 좋은 코드 작성하는 원칙중에 하나입니다. 하지만 단순히 Integer로 상수를 만들어 사용하는 것 만으로는 이런 제약을 줄 수 없습니다.

하지만 Typedef 어노테이션을 사용하면 컴파일 시에 warning을 통해 이를 미리 확인할 수 있게됩니다.

Typedef 어노테이션이란?

Typedef 어노테이션은 어노테이션을 통해 특정 값의 유효성을 컴파일 시 확인할 수 있는 기능입니다. 자세한 사용법은 Improve Code Inspection with Annotations 이 곳에서 확인 할 수 있습니다.

여기서는 간단한 예제를 가지고 사용법을 확인해보도록 하겠습니다.

먼저 어노테이션을 사용하기 위해 build.gradle 파일에 서포트 라이브러리를 추가해줍니다.

1
dependencies { compile 'com.android.support:support-annotations:24.2.0' } 

그리고 아래와 같이 Typedef 어노테이션을 사용합니다. 아래의 예제는 실제 안드로이드 ActionBar 코드 중 일부입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import android.support.annotation.IntDef;
...
public abstract class ActionBar {
...
// Define the list of accepted constants and declare the NavigationMode annotation
@Retention(RetentionPolicy.SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}

// Declare the constants
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

// Decorate the target methods with the annotation
@NavigationMode
public abstract int getNavigationMode();

// Attach the annotation
public abstract void setNavigationMode(@NavigationMode int mode);

NavigationMode라는 Typedef 어노테이션을 생성합니다. NavigationMode에는 @IntDef를 통해 NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS 세 가지 값만 들어올 수 있음을 명시하고 있고, 각 상수는 아래 public static final int로 선언이 되어 있습니다. **@Retention(RetentionPolicy.SOURCE)**는 컴파일러가 .clss에 @NavigationMode의 데이터를 저장하지 않도록 합니다.

이제 NavigationMode는 함수의 반환값이나 인자, 객체의 필드에 사용될 수 있습니다. 코드에서 보면 getNavigationMode() 함수의 반환값과 setNavigationMode() 함수의 인자에 @NavigationMode 어노테이션이 붙어있습니다.

이를 통해 getNavigationMode()의 반환값이나 setNavigationMode()의 mode 인자에 NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS 외의 값이 들어가면 warning이 발생합니다.

이렇게 생성한 Typedef 어노테이션은 안드로이드 스튜디오의 코드 완성에도 적용되어 간편하게 사용할 수 있습니다. @StringDef를 통해 문자열도 동일하게 사용이 가능합니다.

Typedef 어노테이션 사용 시 주의점

Typedef 어노테이션을 사용한다 해도 빌드는 정상적으로 수행됩니다. 따라서 반드시 IDE 자체에서 실시간으로 보여주는 warning을 확인하거나, 안드로이드 스튜디오의 Analyze -> Code Infection을 통해 warning을 확인 하는 것이 중요합니다.

마무리

이 글은 ENUM 사용 시 주의할 점을 정리한 글이지만 ENUM을 사용하지 말라는 글은 절대 아닙니다.

ENUM이 DEX의 크기를 증가시키는 문제는 있지만 ENUM을 사용하면 코드의 가독성과 타입의 값에 대한 유효성(Type safety)이 좀 더 보장됩니다.

이는 굉장한 장점입니다. 따라서 가독성이나 안정성이 중요시 되는 코드에는 ENUM을 사용하는게 맞습니다.

비슷한 예로 C언어의 매크로 함수가 있습니다. 매크로 함수는 함수처럼 사용할 수 있지만 컴파일 시에 직접 코드로 치환되어 실행파일에 삽입됩니다. 따라서 매크로 함수를 사용하면 함수 호출의 부하를 줄일 수 있습니다.

하지만 매크로 함수도 잘 못 사용하면 오히려 성능을 저하시킵니다. 가령 코드의 너무 많은 곳에서 매크로 함수를 호출하게되면 호출 부에 모두 코드로 치환되기 때문에 코드 영역의 크기가 매우 커질 수 있습니다. 즉 경우에 따라 오히려 성능을 떨어뜨릴 수 있습니다.

ENUM과 Typedef 어노테이션 역시 비슷합니다. 각각의 장단점을 보고 필요한 곳에 잘 사용하는 것이 중요합니다. 또한 Proguard를 사용하면 위의 최적화를 대부분 알아서 수행해 줍니다. 어찌보면 굳이 Typedef 어노테이션을 사용할 필요가 없을지도 모릅니다.

이런 관점에서보면 안드로이드 플랫폼 코드에 Typedef 어노테이션을 권장하는 이유를 알 수 있습니다. 안드로이드 플랫폼 코드는 프로가드를 적용하지 않고, 전체 코드의 크기가 굉장히 크기 때문에 ENUM의 사용을 제한하지 않으면 플랫폼 코드가 대책없이 커질 수 있으니 딱 Typedef 어노테이션을 사용하기 좋은 곳이겠죠.

참고 자료

The price of ENUMs (100 Days of Google Dev)

Perfmatter 시리즈 영상 중 ENUM 사용 시 주의점에 대해 설명하는 영상입니다.

Improve Code Inspection with Annotations

구글 개발자 페이지 문서입니다. 어노테이션을 이용해서 빌드 시 코드의 잠재적인 이슈를 미리 확인하는 방법을 다룹니다.