Изоляция
Нам хотелось бы иметь единый исходный код, который бы компилировался без изменений во всех системах, но, к сожалению, это может быть нереально. Однако было бы ошибкой позволить непереносимому коду расползтись по всей программе, как это и происходит при условной компиляции.
Выносите системные различия в отдельные файлы.
Когда для разных систем требуется разный код, его лучше выносить в отдельные файлы – один файл на каждую систему. Например, текстовый редактор Sam работает под Unix, Windows и в ряде других операционных систем. Интерфейсы с системой меняются от среды к среде очень широко, но большая часть кода Sam везде идентична. Все различия, связанные с конкретной системой, вынесены в отдельные файлы: unix. с содержит код интерфейса с системами Unix, a windows, с – со средой Windows. В этих файлах реализуются переносимые интерфейсы с операционной системой, различия же получаются скрытыми. Таким образом, можно сказать, что Sam написан для своей собственной виртуальной операционной системы, которая переносится на различные реальные системы с помощью пары сот строк кода на С, реализующих несколько небольших, но непереносимых операций, вызывающих функции конкретной системы.
Самое интересное, что графические оболочки различных операционных систем не играют почти никакого значения: Sam имеет собственную переносимую библиотеку для своей графики. Несмотря на то что написать такую библиотеку, конечно же, гораздо труднее, чем просто адаптировать код под данную систему (код интерфейса с системой X Window, например, по своим размерам приближается к половине всего остального кода Sam), суммарные затраты для нескольких систем получаются все равно меньше. При этом не надо забывать, что графическая библиотека ценна сама по себе и использовалась для создания и других переносимых программ.
Sam – это довольно старая программа; в наши дни переносимые графические оболочки, такие как OpenGL, Tcl/Tk и Java, доступны для большого числа платформ. Создание кода на их основе вместо использования собственных графических библиотек обеспечит вашим программам большую область применения.
Прячьте системные различия за интерфейсами.
Абстрагирование – мощный способ разграничения переносимой и непереносимой частей программы. Хороший тому пример – библиотеки ввода-вывода, имеющиеся в большинстве языков программирования: они оперируют абстрактным представлением внешней памяти в терминах файлов, которые можно открывать, закрывать, читать или записывать, не затрагивая вопросов их физического расположения или структуры. Программы, которые придерживаются этого интерфейса, будут исполняться на любой системе, где он реализован.
Реализация редактора Sam представляет собой пример абстракции другого рода. Интерфейс описан для файловой системы и графических операций, а программа использует только свойства этого интерфейса. Сам интерфейс использует те возможности, которые имеются в конкретной операционной системе, что приводит к появлению абсолютно разных реализаций для разных систем, но программа, использующая его, зависит только от этого интерфейса, а не от его конкретной реализации, и поэтому ее не требуется изменять при переносе между системами.
Подход к переносимости, реализованный в системе Java, – пример того, насколько далеко можно здесь продвинуться. Программа на Java транслируется в операции "виртуальной машины", модели компьютера, которую можно реализовать на любой настоящей машине. Библиотеки Java предоставляют унифицированный доступ к возможностям системы: графике, пользовательскому интерфейсу, сети и т. п.; библиотеки же отображают этот доступ в возможности локальной системы. Теоретически должно быть возможно исполнить Java-программу (даже после трансляции) где угодно без каких-либо изменений.