На пути к «10x инженеру»: шорткаты, сниппеты, шаблоны

Программирование — творческая работа. Но в ней много рутины: поиск, перемещение и редактирование текста (читай кода), создание файлов, написание часто повторяющихся конструкций. Рутина — часть рабочего дня, но тратит время и выматывает — такое точно счастья не приносит.

Примерно полтора года назад мы стали практиковать изучение шорткатов, хоткеев, сниппетов и файловых шаблонов на уровне iOS-команды «Додо Пиццы». Мы распечатывали листы с расширенными командами (базовые уже знали) и держали их под рукой, чтобы учить. Это вошло в практику и теперь, когда к нам в команду приходят новые ребята, мы тренируем команды во время онбординга (и порой сами узнаем что-то новое). Наш опыт и все команды, которые мы изучали, описал в статье: вы узнаете как использовать не только базовые шорткаты для работы в Xcode, чем могут помочь кастомные сниппеты и как прокачать файловые шаблоны. Получилась «методичка» по командам в Xcode, которая поможет iOS-разработчикам работать эффективнее. Добавляйте в закладки, чтобы посмотреть при случае (а навигация в статье поможет быстро найти то, что нужно).

z5wmn4zrtw0fyncf507j2hc2dla.png

Оглавление


— Шорткаты и хоткеи
— Навигация
— Редактирование
— Отладка
— Кастомизация
— Сниппеты
— Шаблоны
— Бонус фича

Шорткат или хоткей?


Шорткат — то, что дает быстрый доступ к пункту меню (копировать/вставить/удалить).

Хоткей — то, у чего нет аналога в пункте меню/интерфейсе взаимодействия (кастомный биндинг).

Строгой разницы между этими терминами нет, можно смело использовать их как взаимозаменяемые. Но я постараюсь придерживаться тех определений, которые описал выше.

Использование шорткатов трудно переоценить: попробуйте вспомнить, когда вы последний раз копировали что-либо через контекстное меню (ПКМ → Копировать). Эти операции делаются на автомате. Давайте рассмотрим, какие еще операции мы можем довести до автоматизма при работе в Xcode.

В этой статье рассмотрим шорткаты для UIKit-flow. 


Базовые клавиши для активации шорткатов:

  • ⌘ — command;
  • ⌥ — option;
  • ⌃ — control;
  • ⇧ — shift.


Базовые шорткаты:

  • ⌘ + B — build;
  • ⌘ + R — run;
  • ⌘ + U — run unit tests;
  • ⌘ +. — stop build/run/run unit tests;
  • ⌘ + ⇧ + ⌥ + K — clean build folder.


Уверен, большинство из вас знакомы с ними, поэтому перейдём к интересному. Шорткаты разбиты на три блока: навигация, редактирование и отладка.

Навигация


Начнём автоматизацию рутины с шорткатов для навигации:

⌘ + 0 (ноль) — показать/скрыть панель Navigator:

b1011dc8fe2bf407e1eba69173b3fd37.gif

⌘ + цифры — быстрое перемещение по вкладкам панели Navigator:

2ccbca380f9bfefc10c96cd90970a880.gif

Повторим трюк с панели навигации для панели Inspectors и научимся скрывать/показывать её. Просто добавляем option: ⌥ + ⌘ + 0 (ноль):

b3a16050048158062ccbe93c5468764e.gif

С зажатым option мы также можем пройтись по всем вкладкам панели — ⌥ + ⌘ +цифры:

21ad8e570cf8aa10c6d84029094bb4d6.gif

Assistant Editor скрыть не получиться, зато в нём можно делать много крутых штук.

⌘ + ⇧ + O — быстро найдем и откроем файл. Открыть можно не только файлы проекта, но и файлы с документацией iOS:

249ff97f972b2b592e0a5d42c1401834.gif

Если найденный файл хочется открыть рядом, зажимаем ⌥, перед тем, как нажать Enter:

e08e2cc5670410318e0994b7511b935b.gif

Либо, можно зажать ⌥ + ⇧ — в этому случае мы сами выбираем, где необходимо расположить радактор:

2b9f63d074ff6973f77660f2327b6982.gif

Перемещаться между редакторами можно с помощью сочетания ⌘ + J + стрелочки для выбора:

39f99799a7c1ad3ffbe7c4f308d1776c.gif

  • ⌘ + ⌃ + T — позволяет открыть несколько редакторов (зажмите столько, сколько нужно);
  • ⌘ + ⌃ + ⌥ + T — меняет ориентацию для открываемого редактора (если предыдущий редактор открывался горизонтально, то новый откроется вертикально, и наоборот);
  • ⌘ + ⇧ + J — поможет быстро найти файл в структуре проекта.


3416e8c2ede9288f226aad80f7eecae7.gif

  • ⌘ + T — добавить новую вкладку;
  • ⌘ + W — закрыть текущую вкладку;
  • ⌘ + ⇧ + \ — посмотреть все вкладки.


68853b21c69924bfc1ff9d367a004483.png

Рассмотрим навигацию внутри панели, отображающую путь до файла:

65d9805be79574195e5b5e8377bcc685.png

Все взаимодействия с ней происходят путём зажатия ⌃ и цифры.

Для всех сочетаний данной панели (за исключением ⌃ + 1) вы можете использовать поиск: после того как сработал хоткей, начните писать название файла/класса. Так работать с данной панелью гораздо удобнее.


⌃ + 1 — позволяет узнать о суперклассах нашего класса, какие протоколы он поддерживает, кто вызывает его методы, какие экстеншены есть у класса.

3c7be32f84fc4f772baaec2ea1025858.png

  • ⌃ + 2 и ⌃ + 3 — отображает список файлов, по которым мы перемещались. Эти сочетания отлично дополняют два других: ⌃ + ⌘ + стрелочки влево/вправо для непосредственной навигации по истории просмотренных файлов назад и вперёд;
  • ⌃ + 4 — открывает окно для навигации по структуре проекта (мало чем полезно, на мой взгляд, часто проекты имеют большую вложенность);
  • ⌃ + 5 — навигация по файлам внутри директории, в которой открыт текущий файл;
  • ⌃ + 6 — навигация по свойствам и методам класса в текущем файле.


Идем дальше:

  • ⌘ + L — переход к строке кода;
  • ⌘ + курсор на миникарте — показать структуру файла.


a2de9e4d98bc6d49d4fa6e3de1b1faa7.gif

⌃ + ⌥ + ⌘ + Enter — открыть класс для сториборда в отдельном редакторе:

98edf2fd5d4394cc0a92abf76b285285.gif

⇧ + ⌃+ ⌥ + ⌘ или ⇧ + ⌥ + ⌘ + ПКМ — контекстное меню иерархии вьюх в сториборде:

f89358bc7f5b56927fec04d0730f8628.gif

Редактирование


  • ⌘ + стрелки влево/вправо — позволяют быстро перейти в начало/конец строки;
  • ⌥ + стрелки влево/вправо — быстрое перемещение между словами без пробелов;
  • Control + стрелки влево/вправо — перемещение между словами в camelCase


Здесь есть хитрость: изначально эта комбинация в macOS переключает рабочие столы, поэтому для работы хоткея в Xcode, его нужно переопределить, либо изменить в System Preferences.


cf7992cb8f20446a92d191512e95ea61.png
Вот что мы сможем делать в итоге:

184af105326ce13b434a7aa735404526.gif

Все эти сочетания отлично работают с зажатым Shift для выделения. А вот что дальше можно делать с этим выделенным блоком:

⌥ + ⌘+ [ или ] — перемещает выделенный блок/строчку вверх/вниз. Самая крутая фича здесь — автоматический заход в скоупы.

ff32b9ccb1c8e92167691ceeaa4081f7.gif

  • ⌘ + [ или ] — перемещает выделенный блок (строку) влево/вправо;
  • ⌃ + I — автоматическое выравнивание выделенного блока (согласно настройкам в Xcode → Preferences → Text Editing → Indentations);
  • ⌥ + выделение курсором — такая комбинация создает множественные курсоры. Удобно проставлять атрибут доступа в модельке или добавлять/удалять одинаковые части.


5b5f3cf505f4732524c68c08bfec5259.gif

  • ⌘ + / — закомментировать/раскомментировать текущую строку;
  • ⌃ + ⌘ + E — редактировать выделенную сущность (но не забудьте поправить конструктор, его шорткат не затрагивает).


313f157bc4741c930aa3a3cfa0675723.gif

  • ⌘ + F — поиск по файлу;
  • ⌘ + ⇧ + F — поиск по проекту;
  • ⌥ + ⌘ + F — поиск и замена;
  • ⌃ + ⌥ + ⌘ + F — фикс всех ошибок в скоупе (не совсем то, о чем вы подумали, но тоже очень крутая вещь). В данном примере явно указывается модификатор доступа public.


3bdaa85c47125dcdb40447d61df5b63c.gif

⇧ + ⌘ + A — показать контекстное меню действий. В зависимости от того, для кого мы вызываем это меню, Xcode предложит релевантные действия. В списке также можно использовать поиск:  

60db9a6a075984f07f3f19882b9cc355.gif

Отладка


Я заметил, что в нашей команде мало кто пользуется шорткатами для консоли, а ведь это не самое редкое место в работе. Давайте изучать.

Брейкпойнты:

  • ⌘ + \ — добавить/удалить брейкпойнт;
  • ⌘ + Y — отключить (disable) брейкпойнт.


Консоль:

  • ⌘ + ⇧ + c — открыть консоль;
  • ⇧ + ⌥ + Y — скрыть консоль.


Навигация внутри консоли:

  • ⌘ + K — очистить содержимое консоли;
  • s — step into (step);
  • n — step over (next);
  • finish — step out;
  • c — continue;
  • breakpoint disable — отключить все брейкпойнты.


В итоге работа с дебагером будет выглядеть примерно вот так:

a788373c4382208b34d7e34e800e2b11.png

Кастомизация


В настройках Xcode можно задать свои биндинги для операций. Например, зададим хоткей для оборачивания строки в NSLocalizedString. Для этого перейдем во вкладку Key Bindings настроек Xcode и найдем интересующую команду:

7c0427fe88a9123a8c99349202c76972.png

У себя в команда мы решили что это будет сочетание ^ + ⌘ + L. Можем смело использовать:

aca03dc97dcb3e612634745194911d15.gif

Или добавить хоткей для удаления строки целиком — ⌘ + D (⌘ + backspace удаляет только текст):

f2a18e450c0bacc3d4099994e08d310e.png

Возможно не самое очевидное сочетание для такой операции, но использовать удобно:

f19d7aff0ac1340bc8f54c886c1c29ee.gif

Можно не только задавать, но и изменять существующие биндинги. Например, зачем мне шорткат для печати в Xcode? Удалил его, теперь можно забиндить на него что-то полезное.

Xcode позволяет создать несколько биндинг сетов. Можно создать сет и пошарить его в команде, это позволит более эффективно работать в паре. Файлы биндингов расположены здесь: ~/Library/Developer/Xcode/UserData/KeyBindings


Наша команда на этом не остановилась —, а что если забиндить запуск скриптов, например для установки/переустановки подов? В Xcode, в отличие от AppCode, нет встроенного терминала, приходится переключаться между вкладками. Получается, чтобы установить/переустановить поды в проекте, необходимо переключиться на терминал, перейти в папку с проектом и прописать команду. Ужасно долго. Но решение есть!

Воспользуемся другой функциональностью Xcode — Behaviors:

  1. добавим поведение через + внизу слева;
  2. зададим хоткей для вызова (как раз один освободился);
  3. укажем путь до скрипта.


8b320c3bc3bac3e3fea93cbaf954be53.png

В нашем случае скрипт выглядит так: запускаем терминал (если он не запущен), переходим в папку с проектом и можем указывать нужные команды для работы.

#!/bin/sh
osascript <


Теперь можно пользоваться терминалом не выходя из Xcode: ⌘ + P запустит скрипт на переустановку. Зажать сочетание — это всё, что от нас нужно.

Какие еще разделы для шорткатов можно прокачать:

  • рефакторинг — extract to function/method/variable, add missing switch case;
  • брейкпойнты — создание Swift Error/Exception/Symbolic/Test Failure;
  • Version Control Source — push/pull/fetch/new branch/checkout;
  • работа с Swift Package Manager — add/reset/resolve/update.


Как изучать шорткаты в программах? Как минимум, все стандартные приложения macOS имеют вкладку Help с возможностью поиска. Поиск в интерактивном режиме не только проведет вас по всем самым потаённым местам, но и подскажет шорткат.

f06904d496c5d3472c2f5d0ce1a2e637.gif


Сниппеты


Иногда, работая над проектом, я понимал, что раз за разом пишу одно и то же — так называемый бойлерплейт. Этот процесс тоже можно автоматизировать. К счастью, сейчас есть много очень хороших решений и они скорей всего вам знакомы: это и Sourcery для генерации кода по шаблону и библиотеки по типу R.Swift для генерации доступа к ресурсам, и другие фреймворки. Но не всегда есть возможность затащить стороннюю зависимость в проект. К тому же есть бойлерплейт-код, где подобные библиотеки не помогут. Зато поможет стандартный инструмент Xcode — сниппеты.

Сниппеты есть для всех ключевых слов языка Swift (и некоторых других) и стандартных конструкций: циклов, ветвлений, функций. Но часто мне нужен не просто var, а private/public var. Точно также мне не нужен просто @testable, мне нужен @testable import. Другими словами, я бы хотел оперировать уже готовыми конструкциями, а не собирать их по частям.

Изменить стандартные сниппеты нельзя, зато добавить свой совсем просто: выделяем блок кода и в контекстном меню выбираем «Create Code Snippet». Дальше Xcode предложит его настроить: задать имя, добавить описание, платформу, скоуп вызова, и, конечно же, указать комплишн, по которому будет вызываться наш сниппет:

7c258811b83ed6dff3f1939cdb7bbd04.gif

Вот так быстро можно «сгенерировать» код для минимальной работы таблицы (ещё быстрее можно сделать, если сразу в сниппете задать количество строк и возвращать пустую ячейку).

Так выглядит сниппет в библиотеке Xcode (⌘ + ⇧ + L):

58cdf9a600049be3dead79005adbefd6.png

Как добавить плейсхолдер: поместите ваш текст внутри конструкции <# MyDescription #> (лучше добавлять её  уже после того, как вы добавили описание, так как она сразу «схлопывается»).


А вот как в два тапа можно сделать импорт целой пачки фреймворков для юнит-тестов:

0c04c0e009673d990c5e096e9eff0c90.gif

В проекте «Додо Пиццы» мы пишем тесты с помощью Quick/Nimble. Каждый новый спек начинается с импорта 4–6 фреймворков (минимум), и продолжается телом разного уровня вложенности. Импортировать фреймворки мы уже научились, сделаем это и для тела спеки. Всего пара тапов по клавиатуре и сетап для тестов готов:

893b0395cc63c9cfd8b4d291bc716c86.gif

Еще один кейс: в нашем проекте для логирования ошибки необходимо описать её в специальном виде: создаем энумку, наследуемся от протокола Error, задаем кейсы, устанавливаем код ошибки, описываем различные параметры. Каждый раз по памяти писать неудобно:

fdb7634c226df70f311b1684655acabf.png

Для меня это идеальный кандидат для оптимизации своего времени — пусть сниппет делает все за меня:

5e6265c0682f2c91b017788e42d875b8.gif

А сколько еще таких мест можно улучшить (и всё это бесплатно). Несколько личных примеров кандидатов для сниппета:

  • Делегат и data source для таблицы/коллекции (обязательные методы).
  • При объявлении методов протокола срабатывал стандартный сниппет функции (с открытыми фигурными скобками). Но мне не нужны скобки в протоколе, поэтому я добавил свой сниппет, без скобок. Теперь не нарадуюсь.


a7ab1b51eb2b959e0596f598259da473.gif

Часто типы данных и функции нужно объявлять с явным типом доступа. Это можно можно завернуть в сниппет.

ace812a7b6848a6ddb360d1116391ce7.gif

  • weak self для замыканий — guard let weakSelf = self else { return };
  • локализация — оборачивание в NSLocalizedString (основное отличие от хоткея описанного выше в том, что здесь мы указываем параметр для бандла).


71b849078c6a4a3c7a6ed5442719e8d8.gif

Шаблоны


Плавно продолжаем тему кодогенерации кода, но теперь поднимемся на уровень выше — шаблоны файлов.

Шаблоны удобны, но Apple не предоставляет инструменты для их разработки и документации. Но можно сделать все ручками, потому что есть доступ к стандартным шаблонам файлов (и проектов) Xcode. Чтобы их найти, перейдём в каталог по этому пути:

// ⌘ + ⇧ + G в Finder

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/Source/Cocoa Touch Class


Дальше разместим свои темплейты: создаём папку по пути ниже, её имя будет отображаться в окне Xcode. Я назвал папку Helpers:

~/Library/Developer/Xcode/Templates/Files Templates/Helpers


Шаблон для Xcode — это каталог, куда входят:

  • файл шаблона (например, .swift или .storyboard);
  • .plist файл с настройками;
  • иконки двух размеров для отображения в окне Xcode при создании файла.


Чтобы создать свой шаблон, скопируем папку со стандартным шаблоном Xcode  и изменим его под себя. Например, для создания объекта API реквеста у себя в проекте мы используем определенную структуру, где описываем стандартные для запроса вещи: урл, параметры, заголовки.

Часто нам интересны только пара параметров, но всё остальное мы обязаны прописать (обязанность протокола). Как раз для этого случая я создал шаблон файла, который не только экономит мне время, но и сразу подсказывает где и что дописать.

Вот так может выглядеть .plist файл в нашем случае:


http://www.apple.com/DTDs/PropertyList-1.0.dtd">

    
        Platforms
        
            com.apple.platform.iphoneos
        
        Kind
        Xcode.IDEFoundation.TextSubstitutionFileTemplateKind
        Description
        Internet request
        Summary
        Internet request
        SortOrder
        1
        AllowedTypes
        
            public.swift-source
        
        DefaultCompletionName
        File
        MainTemplateFile
        ___FILEBASENAME___
        Options
        
            
                Default
                Some
                Description
                Name of the request
                Identifier
                productName
                Name
                Request name:
                NotPersisted
                Yes
                Required
                Yes
                Type
                text
            
        
    


Добавил код в .swift файл (полный пример на GitHub) и обновил иконки. Теперь можно протестировать создание в Xcode (возможно потребуется его перезапуск). Появилась отдельная секция Helpers с моим шаблоном.

09c7f71005a49daa502f18da6240bc69.png

А вот такое окно мы увидим после выбора шаблона:

8a7b22cbbe9da65cb35a9b21a81b8411.png

Вводим имя для реквеста. В итоге будет создан вот такой файл:

c14c1d970e2de64ec001a0d54ea6e112.png

Только посмотрите сколько работы за меня делают шаблоны!

Пара моментов:

  • У нас есть правило для именования реквестов — постфикс Request в имени. При создании файла мне не нужно об этом заботиться, я только ввожу имя реквеста, и по шаблону будет создан класс с правильным названием и именем файла. GetMenuRequest и GetMenuRequest.swift в данном случае.
  • Класс имплементирует протокол, но большинство параметров — дефолтные. После создания файла, шаблон мне «подсказывает» места, которые скорее всего я хочу изменить.


Рассмотрим более интересное применение шаблонов — многофайловые шаблоны с возможностью кастомизации при создании (code variants).

В проекте «Додо Пиццы» мы отделяем вью от котроллера. Если это новый контроллер, то создаем под него сториборд/ксиб. Чтобы создать один экран, нужно создать минимум 3 файла: вью, контроллер и сториборд. Помимо этого, контроллер должен быть связан с вью, а сториборд иметь ссылки на вью и контроллер. При этом, мне не всегда нужно создавать файл сториборда (если он уже есть), или файл с вью (если она не нужна). В этой ситуации помогут многофайловые шаблоны с кастомизацией.

В нашем случае нужно будет создать 4 папки для шаблона:

  1. просто контроллер;
  2. контроллер и сториборд;
  3. контроллер и вью;
  4. контроллер, вью и сториборд;


Вот так будет выглядеть иерархия:

- ViewController.xctemplate
- — UIViewController
- — UIViewControllerStoryboard
- — UIViewControllerView
- — UIViewControllerViewStoryboard
- — TemplateIcon.png
- — TemplateIcon@2x.png
- — TemplateInfo.plist


Здесь уже привычные файлы с иконкой и конфигурацией, но сами шаблоны хранятся в папках. В зависимости от того, какие параметры мы укажем в Xcode при создании контроллера, выберется нужна папка.

Вот так может выглядеть plist:


http://www.apple.com/DTDs/PropertyList-1.0.dtd">

    
        Kind
        Xcode.IDEFoundation.TextSubstitutionFileTemplateKind
        Description
        ViewController class
        Summary
        ViewController class
        SortOrder
        1
        DefaultCompletionName
        ViewController
        Platforms
        
             com.apple.platform.iphoneos
        
        Options
        
             
                  Identifier
                  viewControllerName
                  Required
                  
                  Name
                  ViewController:
                  Description
                  The name of the view controller class to create
                  Type
                  text
                  NotPersisted
                  
                 
             
                  Identifier
                  cocoaTouchSubclass
                  Required
                  YES
                  Name
                  Subclass of:
                  Description
                  What class to subclass in the new file
                  Type
                  class
                  Default
                  UIViewController
                  NotPersisted
                  
                 
             
                  Identifier
                  View
                  Name
                  Also create View file
                  Description
                  Whether to create a View file with the same name
                  Type
                  checkbox
                  Default
                  true
                  NotPersisted
                  
                 
             
                  Identifier
                  Storyboard
                  Name
                  Also create Storyboard file
                  Description
                  Whether to create a Storyboard file with the same name
                  Type
                  checkbox
                  Default
                  true
                  NotPersisted
                  
                 
             
                  Default
                  ___VARIABLE_viewControllerName___
                  Identifier
                  productName
                  Type
                  static
                 
        
    


А вот что получим в итоге:

7fcd1303ced5357e0f4449ae0365fa0b.png

При такой конфигурации создаться три файла, связанные между собой. В них прописан весь минимально необходимый для работы код: в контроллере есть ссылка на вью, в сториборде есть ссылка на контроллер и вью.

84c49daebb249c02d50572a42595b5f6.png

Сила шаблонов в том, что на уровне создания файла можно конфигурировать, что именно нужно создать. Например, можно создать шаблон для VIPER-модуля и при создании указывать только то, что вам нужно (презентер с интерактором или весь модуль целиком).

Бонус секция для шаблонов


Как раньше упоминал, для шаблонов нет официальной документации и инструментов для их создания. Возможности шаблонов «перебираются» энтузиастами на основе стандартных шаблонов Xcode. Но есть одна фича, описание которой я не видел ни в одной статье по шаблонам (возможно, эта будет первой).

Фича в том, что в шаблоне мы можем сконфигурировать текстовое поле так, что оно будет выступать в роли превью для вводимого текста. Например, это можно видеть при создании нового проекта — поле Bundle Identifier меняется в зависимости от поля Product Name. В моем примере это выглядит так:

48d77aef916526972240f6835df0e089.gif

Потрясающе, ведь теперь я знаю, что мне не нужно дописывать бойлерплейт части ViewController и View.

Сделать это просто: в .plist добавим новое текстовое поле, где в default-параметре укажем переменную для интересующего идентификатора.

// Поле ввода имени ViewController

      Default
      Massive
      Identifier
      productName
      Required
      
      Name
      ViewController:
      Description
      The name of the view controller class to create
      Type
      text
      NotPersisted
      YES


// Превью имени ViewController

      NotPersisted
      Yes
      Default___VARIABLE_productName:identifier___ViewController
      Description
      ViewController
      Identifier
      previewViewController
      Name
      ViewController:
      Type
      static


Выглядит эффектно. Спасибо @ihppie за то, что рассказал про эту фичу.

Итог


С помощью простых и стандартных вещей можно избежать рутины и значительно сэкономить не только свое время, но и время команды. Все рассмотренные приемы позволяют фокусироваться на коде, а не взаимодействии с IDE во время его написания.

  • Тренируйте шорткаты — работа на тачпаде/мышкой будет замедлять вас. Многие операции мышкой и так требуют зажатия клавиш, почему бы полностью не перебраться на клавиатуру?
  • Используйте сниппеты для часто повторяемых, неделимых конструкций, project-specific кейсов.
  • Создавайте шаблоны и управляйте не только создаваемым файлом, но и сопутствующими (code variants).


Код со всеми примерами в репозитории на GitHub.

Делитесь в комментариях своими джедайскими техниками работы с кодом, что используете и в каких количествах, как учили или учите — вместе мы станем на один шаг ближе к 10x инженеру. Чтобы не пропустить следующую статью, подписывайтесь на канал Dodo Pizza Mobile и Dodo Engineering.

© Habrahabr.ru