1. Extracting the source from a native Android application
The source code of a native Android application is not difficult to obtain, but it does require extra tools that are compatible with Windows, Mac, and Linux. My personal favorites when it comes to tools are dex2jar and JD-GUI.
If you’re unfamiliar with these tools, dex2jar will read Dalvik Executable files and convert them to the standard JAR format. JD-GUI will read JAR files and decompile the class files found in them.
To make the extraction process easier to understand, let’s start by creating a fresh native Android project:
android create project --target 19 --name TestProject --path . --activity TestProjectActivity --package com.nraboy.testproject
The above command will leave you with an Android project in your current command prompt path.
Since our project has an Activity class already included, lets jump straight into the build process. You can give me a hard time all day long about using Ant instead of Gradle or Maven, but for this tutorial I’m going to stick with it.
ant debug
The above command will create a debug build typically found at bin/TestProject-debug.apk.
Using your favorite unzip tool, extract the APK file. 7-zip is a popular tool, but if you’re on a Mac you can just run the following from the Terminal:
unzip TestProject-debug.apk
This will leave us with a bunch of files and folders, but one is important to us. Make note of classes.dex because it contains all of our source code. To convert it into something readable we need to run dex2jar on it. You can use dex2jar on Mac, Linux, and Windows, but I’m going to explain from a Mac perspective.
With the classes.dex file in your extracted dex2jar directory, run the following from the Terminal:
./dex2jar.sh classes.dex
Open the JD-GUI application that you downloaded because it is now time to decode the JAR and packaged class files. Open the freshly created classes_dex2jar.jar file and you should see something like the following:
See how easy it was to get to the source code of your native Android APK? Now what can you do to better protect yourself?
The Android SDK ships with Proguard, which is a obfuscation module. What is obfuscation you ask?
Obfuscation via Wikipedia:
Obfuscation (or beclouding) is the hiding of intended meaning in
communication, making communication confusing, willfully ambiguous,
and harder to interpret.
While obfuscation will not encrypt your source code, it will make it more difficult to make sense of. With Proguard configured, run Ant in release mode and your code should be obfuscated on build.
2. Extracting the source from a hybrid Android application
The source code of hybrid applications are by far the easiest to extract. You don’t need any extra software installed on your computer, just access to the APK file.
To make things easier to understand, lets first create a fresh Apache Cordova project and then extract it. Start by doing the following:
cordova create TestProject com.example.testproject TestProject
cd TestProject
cordova platform add android
During the project creation process you are left with the default Apache Cordova CSS, HTML, and JavaScript templates. That is fine for us in this example. Let’s go ahead and build our project into a distributed APK file:
cordova build android
You’re going to be left with platforms/android/ant-build/CordovaApp-debug.apk or something along the lines of platforms/android/ant-build/*-debug.apk.
Even though this is a debug build, it is still very usable. With 7-zip or similar installed, right click the APK file and choose to extract or unzip it. In the extracted directory, you should now have access to all your web based source files. I say web based because any Java files used by Apache Cordova will have been compiled into class files. However, CSS, HTML, and JavaScript files do not get touched.
You just saw how depressingly easy it is to get hybrid application source code. So what can you do to better protect yourself?
You can uglify your code, which is a form of obfuscation.
Doing this will not encrypt your code, but it will make it that much more difficult to make sense of.
If you want to uglify your code, I recommend you install UglifyJS since it is pretty much the standard as of now. If you prefer to use a task runner, Grunt and Gulp have modules for UglifyJS as well.
С каждым днем приложений для Android становится все больше и больше. Миллионы интересных игр и полезных программ можно найти в Play Market. Все они имеют удобный интерфейс и справляются с поставленной задачей. Без них сложно представить современный мир информационных технологий. Эта статья ориентирована на людей, которые не только ценят функционал и внешний вид приложений, а еще и интересуются их внутренним устройством.
Если Вам когда-либо было интересно, что находится “под капотом” любимого приложения, и вы немного смыслите в программировании – эта статья для Вас. Мы расскажем, как посмотреть исходный код приложения Android прямо на вашем гаджете. Поехали!
Общие сведения
Большинство программ для ОС Android, как и большая часть самой операционной системы, написаны на языке программирования Java. А это значит, что посмотрев в исходный код программ Android, мы, скорее всего, увидим Java код с использованием Android SDK (которая включает в себя инструменты платформы Android). Повторюсь: чтобы понимать исходный код приложений, нужно иметь базовые знания Java и принципы работы Android.
Как узнать исходный код приложений Android?
Для начала скачайте приложение, исходный код которого Вас заинтересовал. Затем зайдите в Play Market и скачайте утилиту под названием Show Java. Именно она будет заниматься декомпилированием. Установили? Отлично, а теперь перейдем к самому интересному – извлечению исходного кода Android программы. Запускаем Show Java.
Выберите нужное приложение из установленных, или найдите его на SD карте. Теперь нужно выбрать декомпилятор. Я обычно выбираю CRF. Если возникнут проблемы – пробуйте JaDX.
Начнется декомпиляция программы. Это может занять некоторое время. Чем больше приложение – тем дольше декомпилятор будет доставать исходные коды. Пока вы ждете результата, почитайте о перспективных языках программирования.
По завершению процесса вы получите список пакетов с исходниками Android приложения. Конечно, это не 100% копия кода, которую писали разработчики этого приложения. Но основная логика сохраняется, разобрать не сложно. Что делать с исходниками? Что угодно. Смотрите, разбирайте, возможно Вам будут интересны некоторые “фичи” или особенности реализации функционала программы.
Исходный код раскрыть невозможно если для этого постарались обеспечить безопасность (особенно если это фирменная вещь).
Но есть способы полностью или частично раскрыть код.
- IDA Pro – самый лучший из безплатных взломщиков, некоторые макросы возможно позволяют преобразовать код назад. Но не всегда. Особо хорошо раскрываются borland-продукты.
Hiew.Exe
бесплатная утилита, очень маленькая, позволяет править код на языке ассемблера. Можно править текстовые и целые константы. Можно ею даже сделать “перевод” программы на другой язык, затереть имя фирмы и т.п.- Если есть PDB-файл, или подшита debug-info к файлу то код можно получить в среде разработки (если debug-info полная) редко можно использовать. Исходный код получить нельзя, но можно получить “номера строк и имена исходников”, а так же имена/значения всех констант, локальных переменных, избежать “раздроблености функций” а так же получить классы с начинкой класса (почти все структуры кроме текста программы) что значительно упростит понимание работы программы. Для некоторых библиотек (dll) pdb-файлы можно скачать отдельно. Врядли уважающая себя фирма допустит утечку pdb своего стоящего продукта.
- Debug info, существует DebugInfo-информация встроеная в exe. Если есть такая информация, и соответствующая утилита (Например на Borland Delphi собран, и он есть в наличии), то можно сделать аналогичное предыдущему пункту. Но врядли уважающая себя фирма допустит такую “глупую” ошибку (аналогично pdb).
- Если файл написан на с# его можно почти полностью просмотреть в кодах с помощью disSharp (такие программы “подключают” в таблице импорта лишь mscorlib и всё). DisSharp плохо дизассемблирует некоторые части программы, но возможно его платная версия или платная версия подобных утилит раскрывает код лучше.
- Если файл написан на FoхPro, clipper и других подобный байт-кодовых языках – он раскрывается спец-утилитами (Refox например).
- Утилита
exescope.exe
ResourceHacker.exe
и её-подобные утилиты позволяют смотреть шапку, подключенные библиотеки (по ним можно понять на чём писана программа) и редактировать ресурсы программы (ресурс-формы в.т.ч. delphi, иконки, картинки, таблицы ресурсо-строк). - Если извесно чем создан код – думаю есть специальные утилиты способные его раскрыть (они платные и малодоступные).
Опять-же, это при условии что нету паковщика кода (тогда нужно сначала применить депаковщик), шифровальщика/самомодифицирующегося кода. Если не разбит обфускатором так что не распутать. Чем больше код – тем сложнее разобраться.
P.S. Лично моё мнение – раскрывается-взламывается всё, но на это нужно потратить много-много времени. Возможно год и более (зависит от опыта и инструментов, в свободном доступе хороших инструментов нету).
() translation by (you can also view the original English article)
Общеизвестно, что приложения для Android распространяются в виде файлов пакетов Android Application, или файлов APK для краткости. Тем не менее, большинство людей смотрит на APK как черный ящик и понятия не имеет, как оно создано или что внутри него. Даже у большинства разработчиков приложений есть только поверхностное понимание анатомии APK. В интегрированных средах разработки, таких как Android Studio, требуется всего один щелчок, чтобы преобразовать проект Android в APK.
В этом уроке мы рассмотрим приложение для Android. Другими словами, мы собираемся открыть APK и посмотреть его содержимое. Кроме того, поскольку APK является двоичным файлом, предназначенным только для чтения машинами, я также расскажу вам о нескольких инструментах, которые вы можете использовать для перевода его содержимого в более удобочитаемую форму.
Предпосылки
Чтобы следовать, вам нужно:
- последняя версия Android SDK
- Android-устройство или эмулятор под управлением Android 4.4 или выше
1. Зачем заглядывать внутрь в APK?
Многие люди делают это из чистого любопытства. Другие пользуются возможностью прямого доступа к изображениям, звукам и другим активам своих любимых игр или приложений. Однако есть более важные причины, по которым вы хотели бы заглянуть внутрь APK.
Обучение
Если вы только начали изучать разработку приложений для Android, вы можете многому научиться, просматривая APK-файлы популярных приложений или приложений, созданных профессионалами. Например, просмотрев файлы макета XML приложения, которые хорошо смотрятся на нескольких размерах экрана, вы можете улучшить свои собственные навыки создания макета.
Безопасность
Приложения, загруженные из ненадежных источников, могут содержать вредоносный код. Если вы уже являетесь опытным разработчиком приложений, разобрав такие приложения, вы можете посмотреть их код, чтобы лучше понять, что они действительно делают под капотом.
2. Как создается APK?
Не так много можно узнать из APK без базового понимания того, как он создается. Фактически, наиболее важные инструменты, используемые для анализа APK, также являются инструментами, используемыми для его создания.
Проекты Android состоят в основном из исходного кода Java, XML-макетов, метаданных XML и активов, таких как изображения, видео и звуки. Прежде чем операционная система Android сможет использовать все эти файлы, их необходимо преобразовать в форму, которую она понимает. Это преобразование включает в себя множество промежуточных задач, которые обычно называются процессом сборки Android. Конечным результатом процесса сборки является APK или пакет приложений для Android.
В проектах Android Studio Android-плагин для Gradle обрабатывает все промежуточные задачи процесса сборки.
Одной из первых важных задач является создание файла с именем R.java. Это файл, который позволяет разработчикам легко получать доступ к макету проекта и ресурсам в своем Java-коде с помощью числовых констант. Для создания файла используется инструмент под названием aapt, который является коротким для Android Asset Packaging Tool. Инструмент также преобразует все ресурсы XML вместе с файлом манифеста проекта в двоичный формат.
Все файлы Java, включая R.java, затем преобразуются в файлы классов с использованием компилятора Java. Как вы уже знаете, файлы классов состоят из байт-кода, который может быть интерпретирован механизмом выполнения Java. Однако Android использует специальный тип Android runtime (ART), который оптимизирован для мобильных устройств. Поэтому, как только все файлы классов были сгенерированы, инструмент, который называется dx, используется для перевода байт-кода в байт-код Dalvik, формат, который понимает ART.
После обработки ресурсов и файлов Java они помещаются в файл архива, который очень похож на файл JAR. Затем архивный файл подписывается с использованием закрытого ключа, который принадлежит разработчику приложения. Эти две операции выполняются плагином Gradle без использования каких-либо внешних инструментов. Ключ разработчика, однако, получен из хранилища ключей, управляемого keytool.
Наконец, c файлом архива делается несколько оптимизаций с использованием инструмента zipalign, чтобы убедиться, что память, которую приложение потребляет во время работы, сведена к минимуму. На данный момент архивный файл является допустимым APK, который может использоваться операционной системой Android.
3. Анализ содержания APK
Теперь, когда вы понимаете, как файлы APK создаются и используются, давайте откроем их и посмотрим на его содержимое. В этом уроке мы используем APK приложения под названием Sample Soft Keyboard, которое предварительно устанавливается на эмулятор Android. Однако, если вы предпочитаете использовать физическое устройство, вы можете так же легко использовать APK любого приложения, которое вы установили на нем.
Шаг 1: Перенос APK на компьютер
Чтобы проверить содержимое APK, вы должны перенести его с эмулятора на свой компьютер. Прежде чем вы это сделаете, вам нужно знать имя пакета и абсолютный путь APK. Используйте adb
, чтобы открыть сеанс оболочки на вашем эмуляторе.
Когда вы увидите приглашение оболочки, используйте команду pm list
, чтобы отобразить имена пакетов для всех установленных приложений.
Название пакета приложения, которое нас интересует, это com.example.android.softkeyboard. Вы должны увидеть его в списке. Передав имя пакета команде pm path
, вы можете определить абсолютный путь APK.
1 |
pm path com.example.android.softkeyboard |
Вывод команды выше выглядит следующим образом:
1 |
package:/data/app/SoftKeyboard/SoftKeyboard.apk |
Теперь, когда вы знаете свой путь, вы можете выйти из оболочки и перенести APK на свой компьютер с помощью команды adb pull
. Приведенная ниже команда передает его в каталог вашего компьютера /tmp:
1 |
adb pull /data/app/SoftKeyboard/SoftKeyboard.apk /tmp |
Шаг 2: Извлечение содержимого APK
Ранее в этом уроке вы узнали, что APK – это ни что иное, как сжатый файл архива. Это означает, что вы можете использовать менеджер архива по умолчанию для своей операционной системы. Если вы используете Windows, вы можете сначала изменить расширение файла с .apk на .zip. После извлечения содержимого APK вы должны иметь возможность видеть файлы внутри APK.
Если вы разработчик приложений, многие файлы в APK должны выглядеть знакомыми. Однако, помимо изображений в папке res, файлы находятся в формате, с которым вы не можете работать без помощи нескольких инструментов.
Шаг 3: Расшифровка двоичных файлов XML
Android SDK включает в себя все инструменты, необходимые для анализа содержимого APK. Вы узнали ранее, что aapt
используется для упаковки ресурсов XML во время процесса сборки. Он также может использоваться для чтения большого количества информации из APK.
Например, вы можете использовать опцию dump xmltre
e для чтения содержимого любого двоичного XML-файла в APK. Вот как вы можете прочитать файл макета с именем res/layout/input.xml:
1 |
aapt dump xmltree /tmp/SoftKeyboard.apk res/layout/input.xml |
Результат должен выглядеть примерно так:
1 |
N: android=http://schemas.android.com/apk/res/android |
2 |
E: com.example.android.softkeyboard.LatinKeyboardView (line=21) |
3 |
A: android:id(0x010100d0)=@0x7f080000 |
4 |
A: android:layout_width(0x010100f4)=(type 0x10)0xffffffff |
5 |
A: android:layout_height(0x010100f5)=(type 0x10)0xfffffffe |
6 |
A: android:layout_alignParentBottom(0x0101018e)=(type 0x12)0xffffffff |
Это не XML, но благодаря отступу и ярлыкам, например N для пространства имен, E для элемента и атрибуту A, вы должны быть в состоянии прочитать его.
Шаг 4: Расшифровка строк
На предыдущем шаге вы видели, что дешифрованный XML имеет шестнадцатеричные числа вместо строк. Эти числа являются ссылками на строки в файле с именем resources.arsc, который представляет собой таблицу ресурсов приложения.
Вы можете использовать параметр dump resources
для aapt
для просмотра таблицы ресурсов. Вот как:
1 |
aapt dump --values resources /tmp/SoftKeyboard.apk
|
Из вывода команды вы можете определить точные значения строк, используемых в приложении. Вот запись для одного из шестнадцатеричных чисел в XML:
1 |
resource 0x7f080000 com.example.android.softkeyboard:id/keyboard: t=0x12 d=0x00000000 (s=0x0008 r=0x00) |
Шаг 5: Разборка Dalvik Bytecode
Наиболее важным файлом в APK является class.dex. Это файл, который используется во время работы приложения Android во время запуска приложения. Он содержит байт-код Dalvik, сгенерированный во время процесса сборки.
Разбрав этот файл, вы можете получить информацию о классах Java, используемых в приложении. Для этого вы можете использовать инструмент, называемый dexdump. С помощью следующей команды вы можете перенаправить вывод файла dexdump
в файл, который может быть открыт любым текстовым редактором.
1 |
dexdump -d /tmp/classes.dex > /tmp/classes.dasm |
Если вы откроете classes.dasm, вы увидите, что у него есть сотни строк низкоуровневого кода, который выглядит так:
Излишне говорить, что это очень сложно. К счастью, вы можете изменить выходной формат dexdump
в XML с помощью опции -l
. С помощью следующей команды вы можете перенаправить свой вывод в файл, который можно открыть в браузере.
1 |
dexdump -d -l xml /tmp/classes.dex > /tmp/classes.xml |
Объем информации, доступной в формате XML, меньше, но дает вам представление о классах Java, методах и полях, присутствующих в приложении.
Заключение
В этом уроке вы узнали, как создается APK и что он содержит. Вы также узнали, как использовать инструменты, доступные в Android SDK, для расшифровки содержимого файлов APK. Документации об этих инструментах не так много, но поскольку все они с открытым исходным кодом, вы можете попробовать прочитать их подробный код с комментариями, чтобы узнать о них больше.
Если вы ищете что-то более простое в использовании, вы можете попробовать использовать популярные сторонние инструменты, такие как dex2jar, который генерирует более читаемый дизассемблированный код или JADX, декомпилятор, который может генерировать Java-код.
Вася — молодой программист. Получив задачу и засучив рукава, он берется за написание кода. Уже через день решение задачи готово, Вася запускает его… И сталкивается с неожиданной досадной ошибкой. Вася старательно исправляет ее и повторно запускает решение. В результате — снова неприятная ошибка. Так Вася долго наступает на грабли, пару раз значительно переписывает программу, пока, наконец, через неделю задача окончательно не решена.
Петя — опытный программист. Получив постановку, он ищет, нет ли исходного кода, решавшего аналогичную задачу. Пару дней Петя проводит, читая материалы и разбираясь в чужих модулях, а на третий — запускает свое решение, основанное на уже существующем коде. В нем есть пара незначительных ошибок, которые удается быстро исправить. Ура! Все работает так, как нужно, уже на третий день.
Как найти нужный вам кусок исходного кода? Как его понять? А главное — зачем все это делать? В поиске ответов на эти вопросы добро пожаловать под кат.
Почему важно уметь искать и читать код?
Д. Томас и Д. Спинеллис в книге «Анализ программного кода на примере проектов Open Source» формулируют несколько важных тезисов.
Чтение кода — это повседневное занятие программиста.
Со стороны кажется, что основная работа программиста состоит в написании исходников, но это не так. Чаще всего код приходится читать, а не писать. Вчера ваш товарищ написал модуль, а сегодня перед вами стоит задача усовершенствовать его или исправить ошибку, что невозможно без предварительного изучения трудов вашего товарища.
90 % ошибок можно устранить при чтении кода.
С цифрой 90 %, как мне кажется, можно поспорить, но основная мысль ясна: при чтении (в данном случае — рецензировании) программы можно (и нужно) выявлять и устранять ошибки.
Нас часто учат писать код, но редко — читать его.
И напрасно, поскольку без чтения существующих исходников приходится постоянно изобретать велосипед. Даже если вы можете написать что-то с нуля, гораздо эффективнее будет осмотреться вокруг и использовать чужой исходный код напрямую или как аналогию. Чужой код проверен, отлажен и, скорее всего, уже содержит какие-то «фичи», которые будут полезны и при решении вашей задачи.
Итак, читайте код, чтобы не писать велосипедов. Читайте код, чтобы вносить изменения быстрее. Читайте код, чтобы в программах было меньше ошибок.
О чем эта статья?
Основная цель статьи заключается в том, чтобы систематизировать существующие подходы к поиску и чтению кода. Знание конкретных подходов дает возможность применять те из них, которые наиболее приемлемы в том или ином случае. Кроме того, статья содержит обзор инструментов, которые позволят эффективно применять описанные в статье подходы. Стоит отметить, что искать и понимать исходный код можно и без специальных инструментов, однако их применение делает процесс более наглядным и менее рутинным.
Я расскажу о наиболее универсальных концепциях: иными словами, эти концепции применимы ко всем или почти всем языкам программирования. Некоторые инструменты, которые облегчают поиск и чтение исходников, также универсальны, однако большинство актуально только для Visual Studio и .NET. Тем не менее тот факт, что большая часть инструментов применяется исключительно в VS, не означает, что для других IDE нет подобных средств: я уверен, что они есть, поскольку приведенные в статье подходы универсальны. Также оговорюсь, что все описанные инструменты бесплатны, исключение составляет только ReSharper.
Приведенные в статье примеры кода взяты из приложений ShareX и Greenshot (оба распространяются под лицензией GNU GPL) из репозитория ShareX по состоянию на 30 июня 2015. Я благодарю авторов этих приложений за возможность воспользоваться их кодовой базой. Также благодарю моих коллег, в особенности Дениса Гаврилова и Владислава Иофе, за высказанные ими замечания во время работы над статьей.
Поиск кода
Поиск кода представляет собой процесс, результатом которого выступают части исходного кода, связанные с тематикой решаемой задачи.
Поиск вне кода
Начинать искать исходники можно и нужно вне кодовой базы. Предварительное общение с командой, чтение документации и баг-трекера могут существенно упростить непосредственный поиск кода. Подробнее о работе с артефактами можно узнать из семинара Д. Гаврилова «Практики командной работы: о пользе письменных артефактов». Помимо чтения документации и общения с командой полезно вспомнить, что вы уже знаете о том, что предстоит найти. Например, если предстоит найти экранную форму, то стоит вспомнить, что название экранных форм в вашем проекте обычно заканчивается на Form, а все формы лежат в сборе ”UI”.
Можно запустить приложение на исполнение и искать экранную форму перебором. При переборе программист открывает в случайном (или не очень) порядке формы до тех пор, пока не найдет окно, кнопку, пункт меню или любой другой элемент интерфейса, который, как кажется, использует искомый функционал. Перебор применяется, когда задача поиска размыта или программист плохо знаком с системой. Например, вы ни разу не работали с приложением, а от вас требуется добавить функцию, которая бы экспортировала все открытые картинки в буфер обмена. Логичным представляется для начала исследовать приложение: посмотреть, умеет ли оно экспортировать картинки в буфер обмена по одной, есть ли какие-то настройки, связанные с буфером или экспортом и т. д.
Поиск по ключевым словам
Ключевое слово — это слово или словосочетание из текста документа или запроса, несущее существенную смысловую нагрузку с точки зрения информационного поиска. Необходимая для поиска подборка ключевых слов формируется через общение с пользователями, просмотр экранных форм перебором и другие методы, описанные выше в качестве поиска «вне кода». Ключевые слова можно взять из описания задачи, экранных форм и действий пользователя, текста ошибки или сообщения, SQL-запроса, то есть из любых артефактов, связанных с решаемой задачей.
Чтобы поиск по ключевым словам был максимально эффективным, необходимо подбирать редкие ключевые слова. Например, если вы работаете над проектом, в котором много разных документов (Document), то поиск по слову Document даст множество лишних результатов. Однако бывают ситуации, когда подобрать редкие ключевые слова нет возможности. В этом случае сначала нужно найти множество потенциально полезной информации, а затем сузить поиск. Например, найти все существующие упоминания Document, а затем исключить из результатов тесты, поискать в найденных файлах дополнительные ключевые слова и т. д. В целом следует стремиться найти хотя бы что-то (чуть больше или чуть меньше), а не именно то, что нужно.
В большинстве случаев для поиска по ключевым словам достаточно встроенного в IDE поиска (обычно он вызывается сочетанием клавиш Ctrl + F). Иногда может быть полезен файловый менеджер — для поиска в папках, которые не входят в программное решение, — или поисковые движки: например, OpenGrok и DocFetcher (о них речь пойдет ниже). Так, ReSharper содержит полезную функцию Search by Pattern, с помощью которой можно найти, например, все свойства классов, объявленные как нулябельные перечисления (public TEnum? Enum { get; set; }). Конечно же, не стоит забывать и про мощный механизм регулярных выражений; да, он требуется редко, но иногда здорово выручает.
Давайте поговорим чуть подробнее про поисковые движки, работающие с исходным кодом. Главное преимущество поисковых движков состоит в том, что они позволяют осуществлять гибкий поиск. Например, нужно найти все классы, в которых есть упоминания Clipboard и Image. Обычные средства, встроенные в IDE, дадут вам возможность найти либо файлы с Clipboard, либо файлы с Image. Здесь за скобками оставляем пресловутый механизм регулярных выражений: он может помочь, но конструкция поиска будет громоздкой. Более сложный пример, с которым встроенные средства поиска не справятся: необходимость найти свойства классов, помеченные атрибутом A, но не помеченные атрибутом B. Помимо гибкости поисковые движки увеличивают скорость поиска. Если вашей IDE нужна пара-тройка, а иногда и не один десяток секунд, чтобы пробежаться по всем файлам проекта, то поисковый движок имеет индекс, позволяющий делать то же самое в доли секунды.
Существуют различные поисковые движки по исходному коду, можно почитать их обсуждение на StackOverflow или ознакомиться с подборкой на сайте beyondgrep.com. В своей работе я использую OpenGrok: он бесплатный и умеет работать с различными языками, такими как C#, C, PHP, SQL и с десяток других. Движок состоит из 2-х модулей: модуля индексации исходников и веб-приложения для поиска и навигации по коду. Первый модуль индексирует папки проекта в файловой системе. Веб-приложение работает под Apache, позволяет вводить разные поисковые запросы и просматривать полученные результаты. OpenGrok написан на Java, доступен для скачивания на официальном сайте проекта, а в соответствующих статьях можно прочитать о том, как установить движок под Windows и под Linux.
OpenGrok поддерживает различные виды поиска: по тексту всего документа или только в определениях, то есть названиях классов, функций. Кроме того, он позволяет задавать фильтрацию папок и названия искомого файла, выбирать язык, на котором должен быть написан код и т. д. Этот движок показывает результаты в наглядной форме, сгруппированными по файлам, а также позволяет в один клик перейти к нужной строчке в файле и умеет показывать код с подсветкой или в «сыром» виде.
Поисковый запрос и результаты поиска в OpenGrok по ShareX
У OpenGrok есть несколько ограничений: во-первых, отсутствует интеграция с Visual Studio, во-вторых, он не умеет работать с кириллицей (по крайней мере, мне не удалось добиться этого при помощи настроек). Если вам часто приходится искать русский текст, то можно использовать DocFetcher: данный инструмент не заточен напрямую под поиск в исходниках, но умеет индексировать любые текстовые файлы и отлично справляется с русским языком.
В таблице ниже представлено сравнение возможностей поисковых движков OpenGrok и DocFetcher.
Сравнение поисковых движков
С помощью этой таблицы можно выделить для себя необходимые параметры поиска и выбрать наиболее подходящий инструмент.
Параметр | OpenGrok | DocFetcher |
---|---|---|
Гибкость поиска (через поддержку поискового языка) | Да | Да |
Быстрота поиска (через индексацию) | Да | Да |
Специальные инструменты для поиска кода | Да | Нет |
Работа с кириллицей | Нет | Да |
Интеграция с Visual Studio | Нет | Нет |
Мультиплатформенность | Да | Да |
Лицензия | CDDL | Eclipse Public License |
Поиск по классификаторам
Классификатор — это систематизированный перечень наименованных объектов. Наиболее востребованным программистами классификатором, на мой взгляд, является список всех классов программного решения. Примерами других классификаторов могут быть список членов классов, упорядоченных по классам, список файлов, упорядоченных по папкам и др.
Классы приложения ShareX
С одной стороны, классификатором можно пользоваться как инструментом работы с древовидным списком. Вы «разворачиваете» базовое пространство имен программного решения, далее «разворачиваете» пространство имен нужного модуля и так далее, пока, наконец, не доберетесь до нужного класса. С другой стороны, внутри классификатора можно осуществлять сквозной поиск. Например, вы набираете *Document*, а классификатор показывает все классы, в названиях которых встречается Document.
Если вы работаете с ReSharper, то наверняка знаете про функцию Go to Everything, которая позволяет перейти к любому классу, методу, свойству или файлу во всем программном решении. В Visual Studio «из коробки» есть следующие классификаторы: Solution Explorer (классифицирует сборки, папки, файлы, классы и их члены), Class View (классифицирует пространства имен, классы и их члены) и Object Browser (классифицирует сборки, пространства имен, классы и их члены). Solution Explorer и Class View выдают перечень классов внутри вашего решения, а Object Browser — как в вашем решении, так и в сторонних используемых сборках. OpenGrok позволяет искать по определениям (Definition), в том числе с использованием символов подстановки (*Document*). Еще один классификатор — это обычный файловый менеджер, который отображает артефакты, упорядоченные по папкам.
Когда следует применять классификаторы? Чтобы успешно использовать классификатор, программист должен хорошо знать программный продукт. Особенно важно, в частности, знать соглашения об именах (например, *Form — это экранная форма, а DocumentNameListForm — списочная форма документов типа DocumentName), а также принципы, по которым классы упорядочиваются в папках и пространствах имен (в Dictionaries лежат справочники, а в Documents — документы). Еще одна возможность применения поиска по классификатору — ситуация, когда известно точное название класса или его члена.
Поиск ошибки
Периодически возникает задача найти строчку кода, в которой возникает некоторая ошибка. Как вариант, нужную строчку можно найти по известному номеру ошибки. В данном случае номер ошибки — ключевое слово, по которому осуществляется поиск (про поиск по ключевым словам было рассказано выше). В иной ситуации может быть известна трассировка стека ошибки. В ReSharper есть удобный инструмент Stack Traces, который позволяет просматривать стек и перемещаться по методам, которые вызывались перед тем, как произошла ошибка. Чтобы использовать Stack Traces, необходимо скопировать стек ошибки в буфер обмена, после чего в Visual Studio нажать Shift + Ctrl + E.
Просмотр стека ShareX и навигация по нему в Stack Traces
Если у вас нет информации ни о номере ошибки, ни о трассировке стека, можно запустить проект и включить прерывания по ошибке. Для Visual Studio создано удобное дополнение Exception Breaker, позволяющее включать и выключать прерывание по ошибке одним кликом мыши.
По действию пользователя
Иногда требуется узнать, какой метод вызывается при нажатии кнопки или другом действии пользователя. В этом случае можно сначала найти класс, в котором находится вызываемый метод (например, поиском по ключевым словам или по классификатору), а затем расставить точки останова во всех членах этого класса и запустить приложение. В веб-приложениях в некоторых случаях бывает полезен Fiddler, в частности, в нем можно посмотреть название вызываемой WCF-функции и значения переданных в нее параметров.
Итоги
Перед тем как приступать к поиску кода, стоит потратить немного времени на общение с командой, чтение документации и перебор экранных форм. Это необходимо для того, чтобы собрать ключевые слова, узнать названия классов и методов, без которых дальнейший поиск будет затруднительным. Начинать его стоит с самого простого, при этом можно найти чуть больше или чуть меньше, чем требуется. Искать исходники также можно при помощи ключевых слов или классификаторов. Для поиска ошибок и функций, вызываемых по действию пользователя, есть специальные методики.
В следующей таблице представлены рассмотренные выше методы поиска кода.
Метод | Описание | Инструменты |
---|---|---|
Поиск вне кода | Предварительная подготовка к непосредственному поиску кода, особенно когда задача поиска размыта или программист плохо знаком с системой |
|
Поиск по ключевым словам | Поиск кода, в котором встречаются заданные ключевые слова |
|
Поиск по классификаторам | Поиск классов или других объектов, упорядоченных по категориям (например, по пространствам имен), особенно когда программист хорошо знаком с приложением или известно точное название класса или его члена |
|
Поиск ошибки | Поиск строчки кода, в которой возникает ошибка |
|
Поиск по действию пользователя | Поиск метода, который вызывается по действию пользователя |
|
Понимание кода
Поиск кода неразрывно связан с его пониманием. Понимание необходимо, чтобы выделить из всех найденных действительно полезные для решения задачи блоки исходников, чтобы понять, как надо модифицировать и как можно использовать полученные при поиске результаты.
Понимание без чтения кода
Вне кодовой базы начинается не только поиск кода, но и процесс его понимания. Запуск приложения, чтение документации, баг-трекера, лога приложения или системы контроля версий, общение с командой и (или) пользователями дают хорошее представление о том, что происходит внутри приложения. Так же, как и при поиске исходников, перед непосредственным чтением кода важно вспомнить, что уже известно про разбираемую область.
Чтение «черного ящика»
Чтение кода как «черного ящика» предполагает, что по некоторым внешним признакам мы пытаемся установить, что происходит внутри функции или класса. О назначении объекта можно догадаться, посмотрев на его имя, принимаемые параметры и возвращаемое значение. Например,
public ResultOrError SetToClipboard(Image image)
по всей видимости, помещает изображение image в буфер обмена, а информацию о произошедших ошибках помещает в возвращаемый ResultOrError.
Для чтения «черного ящика» важно знать соглашения об именах, то есть о принятых в проекте префиксах, постфиксах и других частях названий. К «черному ящику» относится чтение описаний или комментариев внутри функций.
Чтобы убедиться в правильности предположений о назначении функции, можно изучить ее использование. Также можно найти, какие действия пользователя приводят к вызову функции (например, пользователь нажал на кнопку «Копировать в буфер») и на основе этих знаний понять, что пользователь ожидает получить в результате выполнения функции (в нашем случае, очевидно, скопировать некий объект в буфер обмена).
ReSharper предлагает мощные инструменты Inspect This и Find Usages, которые позволяют найти места, из которых вызывается функция. Inspect This представляется более удобным, поскольку позволяет легко и наглядно осуществлять дальнейший поиск фрагментов кода, из которых вызываются места, где вызывается функция, и так далее. В самой Visual Studio «из коробки» доступен инструмент Call Hierarchy, который имеет возможности, аналогичные Inspect This.
Места, в которых используется функция (ShareX.UploadManager.UploadFile: 66-80)
- Inspect This (слева);
- Find Usages (в центре);
- Call Hierarchy (справа).
Чтение «белого ящика»
Если в «черном ящике» код метода был для нас «закрыт» (мы изучаем исходники на границе и вокруг функции), то в «белом ящике» предполагается изучение кода внутри исследуемого метода.
При «белом ящике» важен порядок чтения исходников. С одной стороны, чтобы понять код, не надо читать его целиком, поэтому начинать чтение стоит с наиболее важных его частей, пропуская менее значимые строки. С другой стороны, от последовательности чтения зависит быстрота понимания. Чтение слева направо и сверху вниз — не лучший способ погружения в исходники.
Один из приемов чтения «белого ящика» состоит в чтении «вглубь»: вначале читаем код первого уровня, затем переходим ко второму и так далее по уровням.
Чтение «вглубь» (ShareX.UploadManager.UploadFile: 43-64)
Для Visual Studio есть дополнение C# outline, которое позволяет сворачивать и разворачивать текст внутри if, for и других управляющих конструкций («из коробки» Visual Studio умеет сворачивать и разворачивать пространства имен, классы, регионы и методы, но не if, for и подобные им конструкции).
Другой способ чтения кода как «белого ящика» состоит в чтении «от ключевых мест». Как правило, это места, где функция возвращает или вычисляет результат, каким-то образом использует входные параметры, делает вывод в файл, на экран или другое устройство. Ключевые места в разных функциях могут быть различными. Важно уметь их найти и прочитать в первую очередь.
Для Visual Studio есть дополнение RockMargin, которое подсвечивает в редакторе и на полосе прокрутки все места, в которых встречаются вхождения выбранного ключевого слова. Например, вы два раза щелкаете на taskSettings, а RockMargin подсвечивает все места, где встречается taskSettings в файле:
Подсветка RockMargin для ShareXUploadManager.cs
В вашем приложении, скорее всего, есть много однотипных, шаблонных кусков. Это могут быть как общеиспользуемые шаблоны (например, Singleton), так и шаблоны, специфичные для вашего проекта (например, какие-то особенности в построении UI). Понять, что делает шаблон, можно по двум-трем ключевым словам, поэтому шаблоны надо учиться читать «целиком», без «разбора букв». Например, на картинке ниже — класс-Singleton, о чем можно догадаться по двум строкам кода, которые выделены красным.
Класс-Singleton (Greenshot.Drawing.Fields.Binding.DecimalFloatConverter:29-52)
Еще одна последовательность, которую следует соблюдать при чтении исходников, состоит в чтении от простого к сложному. Вначале разберитесь с тем, что понятно и так, а затем переходите к более сложным конструкциям.
В заключение раздела о «белом ящике» я бы хотел сказать несколько слов о чтении при рецензировании кода. Перед непосредственным чтением (особенно если прорецензировать предстоит объемную доработку) следует просмотреть названия всех измененных файлов, а затем понять, в каких файлах произошли ключевые изменения. Чтение необходимо начинать с файлов с ключевыми изменениями, из которых вы схватите общую суть и лучше поймете все остальные доработки.
Улучшение кода
Чтобы понять исходный код, можно не только читать его, но и улучшать. Самый простой способ улучшить восприятие кода — правильно расставить отступы. Другой способ улучшить читабельность — рефакторинг. Простейшие переименования и разделение длинных функций на несколько коротких позволяют быстро сделать текст программы более понятным.
Для понимания сложных логических конструкций можно применять законы де Моргана:
!(a && b) = (!a) || (!b)
!(a || b) = (!a) && (!b)
В качестве инструмента по улучшению кода можно порекомендовать ReSharper, который справляется с улучшениями разной степени сложности.
Режим отладки
Режим отладки дает возможность проверить сделанные во время чтения исходников предположения. Чтение в режиме отладки предполагает использование таких инструментов, как точки останова (в том числе точки останова «по условию», например, срабатывающие только при определенном значении переменных), просмотр трассировки стека, пошаговое выполнение программы, просмотр и модификация значений переменных.
Общая картина
При чтении важно поддерживать общее представление о том, что происходит в разбираемом модуле, и уметь быстро перемещаться по разным местам «паззла».
Чтобы удержать в голове общую картину, можно использовать диаграммы, как уже существующие в проекте, так и новые. Некоторые диаграммы можно строить автоматически, особенно это актуально для SQL-запросов. В PL/SQL Developer, например, есть Query Builder, который показывает, какие таблицы участвуют в запросе и как они между собой связаны. Автоматизированно можно строить диаграммы классов, к примеру, в Visual Studio для этого применяется инструмент View Class Diagram. Однако чаще всего достаточно даже нарисованной от руки схемы.
Читать вызываемые методы удобно при помощи ReSharper, Inspect This или встроенного в Visual Studio View инструмента Call Hierarchy. В разделе про «черный ящик» эти инструменты уже рассматривались, но там исследовались места, из которых вызывается функция. Здесь же речь идет о просмотре методов, которые вызываются внутри функции. Inspect This и View Call Hierarchy дают целостную картину — вы видите дерево вызовов целиком — и позволяют быстро по нему перемещаться.
Для быстрых переходов по произвольным блокам кода можно закреплять вкладки (чтобы отличать те 5–10 классов, с которыми вы плотно работаете, от других 20–100, которые пришлось открыть в процессе работы), делать закладки в редакторе или записи от руки, например, выписывать названия классов и их членов.
Для быстрых переходов внутри файла будут полезны: в ReSharper — File Structure (отображает члены текущего класса), в Visual Studio — Member (аналог File Structure) или RockMargin (отображает уменьшенный исходный код на широкой полосе прокрутки, подкрашивает закладки и точки останова).
Итоги
Перед непосредственным чтением исходников важно обратиться к различным источникам вне кодовой базы: пообщаться с командой, почитать документацию, запустить приложение на выполнение. Это даст некоторое базовое понимание приложения, которое поможет в непосредственном чтении кода. Читать исходники можно как «черный ящик», анализируя названия методов, принимаемые ими параметры и т. д. как «белый ящик», производя разбор того, что находится внутри метода. Для облегчения восприятия кода его можно улучшить, например, расставить отступы. Запуск приложения дает возможность проверять сделанные предположения в режиме отладки. Наконец, при чтении важно удерживать общую картину в голове и уметь быстро перемещаться по разным частям исследуемого модуля.
В следующей таблице представлены рассмотренные выше методы понимания кода.
Метод | Описание | Инструменты |
---|---|---|
Понимание без чтения кода | Получение общего представления о модуле без просмотра исходников |
|
Чтение «черного ящика» | Понимание происходящего внутри функции или класса по некоторым внешним признакам (имя, принимаемые параметры, возвращаемое значение и т. д.) |
|
Чтение «белого ящика» | Изучение кода внутри исследуемого метода |
|
Улучшение кода | Улучшение читабельности кода |
|
Режим отладки | Запуск приложения в режиме отладки |
|
Общая картина | Удержание общего представления о том, что происходит в разбираемом модуле; быстрое перемещение по разным местам кода |
|
Заключение
Поиск и понимание кода — важный этап в работе над программным продуктом. Поиск и понимание начинаются за пределами репозитория: с общения с командой, чтения документации, баг-трекера, запуска приложения на исполнение. Поиск и понимание необходимы как при доработке существующих модулей (чтобы доработать существующий модуль, надо понять, как он работает), так и при разработке нового функционала: да, новый модуль иногда можно разработать, не прочитав ни строчки кода, но в проекте, скорее всего, есть отдельные блоки, реализующие схожий функционал, которые можно взять в качестве шаблона для реализации функций и классов.
По моему мнению, чтение кода в широком смысле этого слова (то есть как поиск исходников, так и их понимание) должно выделяться разработчиком в отдельный этап работы над задачей. Результатом этого этапа является набор закладок в методах и файлах, который будет полезен при работе над задачей, а также общее представление о работе некоего блока кода. Уже в рамках непосредственного программирования поиск исходников необходимо продолжать. Например, так: встретил новый класс, нашел места, где он используется, обновил список полезных закладок. Возможно, подбор закладок можно выделить отдельной функцией в команде: технический лидер или ответственный разработчик составляет список полезных закладок, после чего программист берет задачу в работу.
Как для поиска, так и для понимания исходников важно знать соглашения об именах и способах упорядочивания классов (по папкам, пространствам имен). По опыту автора, такое понимание приобретается в процессе работы в проекте, однако соглашения можно формализовывать и сообщать новым разработчикам так же, как соглашения по оформлению кода (Code Style Guide).
Перспективным представляется разработка автоматизированных средств, выявляющих фактически сложившиеся соглашения об именах и способах упорядочивания классов. Как кажется, полезными будут средства, выявляющие наиболее часто встречающиеся названия папок, суффиксы, постфиксы и т. д.
При чтении кода, как и при чтении обычной книги, важно осознать и держать в голове цель чтения. Для чтения кода можно применять некоторые приемы из статей о способах чтения обычного текста (см., например: «Как улучшить свою способность понимать прочитанный текст?»).
Насколько можно оценить, поиск и понимание кода является темой, слабо затронутой в отечественной и зарубежной литературе. Надеюсь, что представленная статья была полезной в освоении приемов эффективной работы с кодовой базой.
Инструменты
Для поиска кода
- OpenGrok
- DocFetcher
- Fiddler
- ReSharper
- Go to Everything
- Stack Traces
- Search by Pattern
- Visual Studio
- Встроенный поиск (Ctrl + F)
- Solution Explorer
- Class View
- Object Browser
- Точки останова
- Дополнения к Visual Studio
- Exception Breaker
- Файловый менеджер
- Перебор экранных форм
Для понимания кода
- ReSharper
- Inspect This
- Find Usages
- File Structure
- Инструменты для рефакторинга
- Visual Studio
- Call Hierarchy
- View Class Diagram
- Member
- Средства отладки
- Дополнения к Visual Studio
- RockMargin
- C# outline
- Запуск приложения
Общие методы
- Общение с командой или пользователями
- Чтение документации и баг-трекера