Pull to refresh

Кросс-языковая разработка ПО

Reading time5 min
Views17K

Задача


Вот бы, разрабатывая программу на одном языке, сразу получать исходники на других языках программирования… Я пишу на C# .NET, но в последнее время всё больше требуется интегрироваться с Java. Одно из решений — оформление web-сервисов для взаимодействия, но не то это, не то. Вроде и существуют конвертеры C# в Java, но эксперимент показал, что для реального проекта они (те, что удалось попробовать) не работают, хотя на «hello world» отрабатывают отлично. Переписать с нуля на Java весь проект нереально — он активно разрабатывается более 6 лет (Pullenti — обработка естественного языка), да и на C# он нужен. Пришлось мобилизоваться и в прошлом году написать этот конвертер, а в этом году и конвертер C# в Python.

Опыт создания конвертера C# в Java


Итак, на входе проекты (solution) Visual Studio, нужно получить классы на Java. Разумеется, не все типы проектов возможно поддержать, но тип «Class Library» и «Console Application» — вполне. Проекты не должны использовать никакие графические библиотеки типа System.Drawing, никакие Windows-возможности или DllImport, а для используемых библиотек должны существовать аналоги в Java. К счастью, для используемых мной возможностей такие аналоги нашлись (работа с XML, архивы GZip и др.). От некоторых несущественных деталей пришлось отказаться, немного подправив алгоритмы. На уровне языка C# и Java близки, но Java чуть беднее — вот что в нём отсутствует:

  • property { get; set; } — приходится работать с функциями;
  • struct — структуры отсутствуют, поэтому сначала я их моделировал просто классами Java, но затем переписал алгоритм, отказавшись от них полностью;
  • delegate — отсутствуют, но моделируются интерфейсом с одной функцией;
  • event — моделируется массивом ArrayList из элементов класса, реализующего интерфейс соответствующего delegate;
  • out и ref аргументы у методов — моделируется классами с одним полем Value;
  • internal — этот модификатор вообще не имеет смысла в Java, так как отсутствует понятие сборки, поэтому просто заменяется на public;
  • new Class() { присваивания } — приходится создавать статическую функцию, в которую выносить эти присваивания;
  • enum — если в C# возможны побитовые операции с его элементами, то в Java это запрещено, и конвертеру приходится enum преобразовывать в классы Java;

Но не это оказалось самое сложное — проблема с огромным количеством системных функций, для которых нужно было искать аналоги. Для некоторых аналогов не нашлось, и пришлось писать их на Java, создавая свою библиотечку, которая добавляется при генерации к результирующим исходникам Java. Например, в C# для потоковых операций ввода-вывода есть базовый класс Stream, а в Java два отдельных класса InputStream и OutputStream, и поэтому нужна некоторая «обёртка» над ними.

Если нужно было исключить из кода C# некоторый фрагмент или оформить его немного по-другому для Java, то использовались директивы препроцессора #if JAVA… #else… #endif.
Вот так, постепенно дополняя конвертер новыми функциями и подправляя исходный код на C#, удалось прийти к тому, что генерируемые на Java более чем 1500 юнит-тестов стали отрабатывать правильно, как и их прототипы на C#.

Сравнение скорости работы .NET и Java


Теперь самое интересное. Есть трудоёмкий алгоритм (в основном работа со строками), и абсолютно идентичные его реализации на C# и Java. Как «апологет» .NET, я всегда сомневался в эффективности Java. Но оказался неправ — C# всего на 15-20% быстрее Java (запускал из Eclipse под Windows), а не в несколько раз, как ожидалось. А какова ситуация с Python? Читайте дальше.

Опыт создания конвертера C# в Python


В Python оказалось конвертироваться даже проще, чем в Java. Основа была готова, осталось только подправить генератор на новый язык и найти (написать) аналоги используемых функций.

Что в Python отсутствует или неудобно:

  • for(init, cond, incr) — в полноценном виде отсутствует, есть некоторые частные случаи с range, но в основном приходится моделировать через while;
  • switch — отсутствует, моделируется if-then-else-ами;
  • interface — отсутствует, но это просто класс с пустыми методами;

Очень неудобная система импорта других классов, приводящая в ряде случаев к циклической зависимости и невозможности импорта вообще. Приходилось отслеживать эти зависимости и выносить некоторый импорт на уровень методов, а не всего файла с классом. Несмотря на эти и другие сложности, удалось добиться правильного отрабатывания тех 1500 юнит-тестов.

Сравнение скорости работы .NET и Python


Многие жалуются на низкую скорость работы Python, но насколько она низка? И низка ли? Пришло время сравнить на том же алгоритме, что и для Java (точнее, на тех же юнит-тестах). Готовы? Итак, Python работает в 30 раз медленнее, чем .NET (запускал в Eclipse под PyDev).

Причина, думаю, в следующем. В Python отсутствуют простые типы как ValueType (насколько я понял), и даже обычные числа — это полноценные объекты (фактически как boxing в .NET). И элемент строки — это не символ, а тоже строка из одного символа! То есть string[i] — это не char, а как бы string.Substring(i, 1) в .NET.

Технология кросс-языковой разработки


На основе личного опыта скажу, что возможно разрабатывать на одном языке и платформе и автоматически переносить на другие языки и платформы. Свой базовый проект разрабатываю на .NET Framework 4.0, и в любой момент имею функционально эквивалентные:

  • исходники на Java (конвертер);
  • исходники на Python (конвертер);
  • проекты на .NET Core (отдельные csproj-файлы, но исходники те же);
  • проекты на .NET Framework 2.0 (конвертер, и такое нужно для одного заказчика);

Поделюсь одной полезнейшей возможностью Visual Studio, которой почему-то мало пользуются даже профессиональные разработчики C#, к моему глубокому удивлению. И которая отсутствует в средствах разработки, скажем, для Java. А именно — возможность изменять код и перемещать текущую позицию прямо в ходе выполнения, причём состояние всех переменных сохраняется и можно продолжить выполнение с учётом внесённых изменений. Это кардинальным образом облегчает отладку, особенно когда для воспроизведения нужной ситуации требуется некоторое время. Например, я включаю режим, при котором Visual Studio приостанавливает программу в месте возникновения Exception (вернее, я этот режим никогда не отключаю), запускаю длительную обработку, а через сколько-то минут или часов возникает эта ситуация, чаще всего NullReference. Я исправляю код, и вместо того, чтобы перезапускать программу, перемещаю точку выполнения на нужный оператор назад и запускаю выполнение дальше. Да и вообще частенько я прямо в ходе выполнения реализую сложные алгоритмы в нужных местах. Помнится, когда делал систему биржевой торговли, то это на порядок облегчало жизнь в условиях трудоёмкой процедуры инициализации и выхода на нужную алгоритмическую ветвь. В Visual Studio эта возможность достигается за счёт тесной интеграции IDE и компилятора — они могут себе такое позволить. Но вернёмся к технологии.

Ограничения очевидны — только библиотеки и консольные приложения. Почти нереальны конечные приложения с пользовательским GUI или Web-приложения. Да и при разработке библиотек приходится придерживаться ограничений, накладываемых требованием универсальности. От чего-то придётся отказаться, что-то переписать, но результат стоит затраченных усилий!
Tags:
Hubs:
+24
Comments81

Articles

Change theme settings