Разработка
Портирование Graphing Calculator с C++ на Swift
Стоило ли мое время портирования на Swift? Мне понравилось изучать Swift, и теперь я намного больше доволен состоянием кода. Писать на Swift — сплошное удовольствие.
Graphing Calculator начался в 1985 году, он был написан на C для Macintosh 128K, во времена 16-битных целых чисел, черно-белого Quickdraw и 8-мегагерцового процессора 68000 без MMU, FPU или GPU. Это было более простое время. С тех пор многое изменилось.
Я давно придерживаюсь философии «Если это не сломано, не чини это», поэтому код несет в себе много пережитков своего прошлого — решений, которые имели смысл в то время, но больше не используются. Процессор сменился с Motorola 68K на семейство IBM PowerPC, затем на Intel, а затем и на ARM. Первоначально калькулятор был написан для классического Mac API по Inside Macintosh, затем для Carbon, затем Cocoa, AppKit и UIKit, а теперь для SwiftUI.
Легче писать новый код, добавляя новую функциональность, и скрывать старый унаследованный код под слоями абстракции. Но в конце концов, десятилетия накопленного технического долга делают новые разработки чреватыми. Graphing Calculator по-прежнему использует API-интерфейсы кооперативных потоков классической Mac OS 9 для запуска кода, замороженного в 1980-х годах, который не является потокобезопасным. Переписать все с нуля, то есть убрать и уничтожить весь код, почти никогда не бывает хорошей идеей. Устаревший код воплощает в себе десятилетия тяжело усвоенных уроков, которые нынешние разработчики еще не прошли, и даже первоначальные разработчики, если они все еще существуют, давно забыли. Хотя новый старт может быть эстетически приятным, он создает огромное поле для ошибок. В типичном минорном релизе легко сосредоточиться на тестировании новых функций. С полным переписыванием все новое. Тем не менее, после тридцати пяти лет запихивания проблем под ковер, я решил, что лучший путь вперед — это пересмотреть все и переписать все с нуля.
C++ был и всегда остается более эффективным языком для управления сложностью в больших проектах, так почему же я сменил язык? Я был невероятно впечатлен технологией дополненной реальности Apple. После добавления поддержки AR в наш продукт для iOS, вдохновленный использованием AR в детских рассказах, я создал прототип приложения, исследуя, как AR можно использовать в математическом образовании.
Enhancing textbooks with #augmentedreality #edtech #mathchat #GrapherApp pic.twitter.com/11dxGNcAIC
— Ron Avitzur (@RonAvitzur) April 15, 2020
Приложение было в основном на C++ и ObjectiveC++. Прототип использовал ARKit для машинного зрения и машинного обучения, что, хотя и возможно в Objective-C, было бы проще в Swift. Было ясно, что так будет верно для всех новых технологий Apple.
Я изучил Swift, портировав основную систему компьютерной алгебры Graphing Calculator. Это началось как учебное упражнение, а затем превратилось в технико-экономическое обоснование. Пандемия сыграла роль в этом решении, так как это стало моим проектом по побегу от пандемии. Рефакторинг можно было бы выполнить на C++ и Objective-C++, но это было бы не так эффективно и не так весело. Порт объединяет множество переходов:
From | To |
---|---|
C++/ObjC/ObjC++ | Swift |
Lex/YACC | Swift |
pthreads | Swift structured concurrency |
C++ char | Swift String |
AppKit/UIKit | SwiftUI |
OpenGL | SceneKit & Metal |
Он также включал рефакторинг и переписывание основных алгоритмов, которые стали громоздкими из-за постепенного развития их функциональности.
Последние 18 месяцев я работал над переписыванием всего. Вот что я узнал.
- Мне нравится синтаксис Swift. Так много повторяющегося шаблонного кода, который был необходим для C++, растворился в Swift, оставив только код, необходимый для представления логики, что сделало смысл более ясным.
- Использование Swift типов значений в классах коллекций упрощает их рассмотрение. Синтаксический сахар делает их использование невероятно простым, и они все еще поддерживаются реализацией, которая использует автоматический подсчет ссылок с копированием при записи, чтобы сделать их производительными практически для всех применений (обнаружение ограничений этого оператора по-прежнему является серьезной проблемой оптимизации производительности Graphing Calculator).
- Использование строк Swift со встроенной поддержкой Unicode заменило путаницу символов C++, представлений UTF-8 и UTF-16, улучшив организацию кода и упростив его понимание.
- ARC, вывод типов, необязательные параметры, замыкания, перечисления со связанными значениями, отсутствие необходимости в файлах заголовков и Swift Concurrency — все это также в значительной степени способствовало написанию лаконичного, выразительного кода.
- В конце концов, порт намного удобнее в сопровождении, удобочитаемее и компактнее. Когда я портировал отдельные разделы функциональности, размер исходного кода Swift обычно составлял 30% от размера соответствующего кода C++ (хотя количество строк кода не является очень информативным показателем, его легко измерить). Меньше кода означает меньше отладки, меньше чтения и понимания, и одно это облегчает поддержку порта.
- При использовании SwiftUI, контроллеры представления полностью исчезают: большая победа декларативного программирования над императивным. В целом исходный код сократился со 152,000 до 29,000 строк без существенной потери функциональности или производительности.
- Самой большой проблемой порта было достижение сопоставимой скорости. Десятилетия итеративной доработки и низкоуровневой оптимизации в каждом выпуске установили высокую планку производительности. Работа с о множеством небезопасных API-интерфейсов Swift в критически важном для производительности коде была сложной, но эффективной. Самая большая нерешенная задача — свести к минимуму накладные расходы на retain/release в ARC при навигации по деревьям выражений. Использование ARC значительно упростило код. Код C++ управлял памятью выражений вручную, что было чрезвычайно хрупким, но при этом очень быстрым. Версия Swift меньше по размеру, в ней легче писать правильный код, но есть разделы, критически важные для производительности, где я знаю, что обход дерева не изменяет никаких счетчиков ссылок, однако у меня нет возможности сообщить компилятору, что накладные расходы на сохранение/освобождение ARC не нужны.
- Язык, библиотеки и среда выполнения Swift имеют отличную документацию, и в крайнем случае можно даже проверить реализацию с открытым исходным кодом. Напротив, среда SwiftUI имеет закрытый исходный код. Когда SwiftUI работает, это почти волшебное наслаждение, но когда он ведет себя неожиданно или когда его поведение выходит за рамки желаемого, может быть трудно понять и обойти его ограничения.
Стоило ли мое время портирования на Swift? Мне понравилось изучать Swift, и теперь я намного больше доволен состоянием кода. Писать на Swift — сплошное удовольствие. С 80-х я намеревался в конечном итоге открыть исходный код. Когда я решил сделать это с кодовой базой C++, я понял, что это не будет полезным вкладом из-за накопившегося за десятилетия технического долга, делающего код C++ непригодным для сопровождения. Теперь я уверен, что новый код можно превратить в полезные автономные пакеты Swift для математических выражений, редактирования, числовых и символьных вычислений и построения графиков.
Swift оправдал свое обещание обеспечить безопасный, быстрый и выразительный код. SwiftUI оправдал свое обещание обеспечить отличный пользовательский интерфейс на платформах Apple с минимальным кодом. Я хотел бы поблагодарить всех, кто внес свой вклад в Swift, за всю их тяжелую работу. Программировать на Swift действительно гораздо веселее. Особая благодарность всем, кто тратит время на ответы на нубские вопросы на форумах Swift и в Twitter. Я не могу выразить свою благодарность за вашу терпеливую и профессиональную помощь на протяжении всего этого процесса.