В Keyhole Software мы в значительной степени компания по модернизации. У нас есть консультанты, которые специализируются на переходе от старого к новому, обновлении ветхого кода и разработке более яркого будущего для предприятий, которые большую часть своего существования были заблокированы поставщиками.
В качестве интересного побочного эффекта этого опыта мы натолкнулись на некоторые повторяющиеся модели и стратегии того, как подходить к модернизации устаревших систем.
В этом блоге мы расскажем о стратегии, которая сейчас очень популярна, Re-Platforming , и продемонстрируем ее с помощью продукта Keyhole Labs, который мы разработали. Основной поток этого поста будет:
- Введение в модернизацию
- Высокоуровневое определение стратегии ре-платформера
- Пример повторного преобразования с использованием Keyhole Syntax Tree Transformer
- Заключительные аргументы
- Резюме
«Я возьму одну модернизацию, пожалуйста … не надо ждать, может быть, две …»
Когда мы впервые привлекаем клиентов к теме Модернизации, мы получаем совершенно разные определения того, чего они на самом деле хотят достичь в процессе. Они варьируются от выхода из приложения для мэйнфреймов до перехода от архитектуры ESB / классической SOA к облачной реализации PaaS, до перехода на архитектуру DevOps / Microservices из архитектуры, заблокированной поставщиком или многоуровневой.
Все эти сценарии происходят с большей частотой, так как компании, которые недавно обновили свой технологический стек, столкнулись с некоторыми из ключевых проблем успешной работы или роста:
- Проблемы развертывания: все должно быть развернуто как единое целое и является болезненным процессом и / или тесно связано со всей его инфраструктурой
- Проблемы масштабируемости: вертикальные пределы масштабируемости нарушаются — это означает, что машины не могут расти достаточно быстро, чтобы справиться с увеличением емкости
- Проблемы с производительностью: объем сообщений / транзакций через систему увеличивает задержку и в некоторых случаях вызывает каскадные сбои
- Ресурсные проблемы: инженеры и программисты, которые работали над этой системой изначально, больше не работают или выходят на пенсию, и язык программирования больше не преподается в школах
Таким образом, введите инициативы по модернизации. Давайте начнем с рассмотрения стратегии Re-Platforming, ее преимуществ и недостатков.
«Re-платформа? Это как починить мои ботинки?
Re-Platform иногда называют лифт-и-сдвиг. По своей сути, ре-платформа переносит, то есть переводит один язык кода на другой. В качестве стратегии модернизации это означает преобразование старых языков кода в новые.
Мейнфреймы по-прежнему распространены на некоторых крупных предприятиях по множеству причин, и из-за этого все еще существуют более старые базы кода, такие как COBOL. Причины отказа от этих старых кодовых баз и мэйнфреймов обычно заключаются в следующем:
- Проблемы с ресурсами (как уже упоминалось выше): программистов на мэйнфреймах становится мало, и эти языковые наборы не охватываются современными учебными программами. Труднее привлечь новых разработчиков, особенно когда нормой становятся быстрые изменения и постоянно расширяющийся выбор технологий. Меньшее количество сотрудников готовы работать над тем, что некоторые считают устаревшей технологией.
- Мейнфреймы могут быть большими затратами для предприятий любого размера, при этом единственным вариантом роста является вертикальное масштабирование, которое иногда обходится слишком дорого.
Стратегии аварийного восстановления и обеспечения высокой доступности, характерные для большинства современных архитектур, могут быть слишком дорогостоящими для мэйнфреймов. - Новые шаблоны программирования не могут быть легко использованы в процедурных языковых конструкциях (ООП, Функциональное программирование, Реактивное программирование и т. Д.), Что ограничивает возможности.
- Изменение в SDLC — то есть переход от водопада к гибкому процессу, чтобы оставаться конкурентоспособным.
Итак, чтобы сделать длинную историю бесконечной — что мы на самом деле имеем в виду, когда говорим «Переплатформирование»?
Это процесс, в котором старые базы кода анализируются для определения грамматики или шаблонов в базе кода.
Как только дерево грамматики или набор шаблонов кода определены, исходная база кода (например, COBOL) запускается через одно- или многошаговое программное обеспечение компилятора-компилятора для преобразования унаследованного кода в желаемое конечное состояние — обычно Java, C # или более новый языковой эквивалент.
С точки зрения бизнеса это может быть очень привлекательным. Вместо того, чтобы укомплектовывать команды владельцев продуктов и разработчиков, чтобы постепенно переписывать каждый из унаследованных кусков кода на новом языке — этот метод обещает выполнить всю тяжелую работу с парой нажатий кнопки. Звучит здорово!
Ну, подожди секундочку, профессор — у этого подхода есть свои проблемы, о которых нужно упомянуть, прежде чем мы продолжим. Труднее всего понять:
Перевод кода не обязательно исправляет технический долг!
В некоторых случаях эти унаследованные кодовые базы могут существовать более 20 лет. Это потенциально 20+ лет плохих или специфичных для мэйнфреймов решений, встроенных в ваш код.
Весь процесс перевода даст вам те потенциальные мины кода, которые теперь существуют на более новом языке, которые могут не извлечь выгоду из щедрости и мощности мэйнфрейма.
Код может выглядеть хуже, чем на мэйнфрейме!
Выполнение кода через этот процесс может иногда выглядеть так, как будто его выбросили через дробилку древесины. Некоторые конструкции и поведения мэйнфреймов и унаследованных кодов плохо или вовсе не переводятся в более новые кодовые базы. (Например: в недавнем клиенте мы нашли пример, где в одной базе кода математическая операция x / 0 вернула 0!)
Даже если код конвертируется и выглядит хорошо, это не значит, что он всегда будет работать!
Простой перевод на другой язык не гарантирует выполнения — первоначальный успешный перевод обычно означает отсутствие синтаксических ошибок.
Некоторое изменение, возможно, потребуется дополнительная инфраструктура, чтобы помочь коду работать и строить.
Бег! = Выполнение
Опять же, если мы запустим его и создадим, все может показаться отличным в нашей пилотной конверсии. Как только мы добавим миллионы транзакций и записей в него для обработки — вы найдете все дыры в корзине.
Сложность, скорее всего, не будет уменьшена этим процессом!
Во время этого процесса вы, скорее всего, переходите от чего-то, что обрабатывает всю его сложность в процессе (и в некоторых случаях с небольшими или нулевыми штрафами ввода / вывода), к чему-то менее щедрому с его ресурсами.
Перемещение этих кодовых баз на более новые языки, как правило, предполагает некоторое разделение задач:
- уровень доступа к данным, противоположный встроенным операторам SQL
- потенциальные новые хранилища реляционных данных в отличие от хранилищ данных на основе файлов
- Уровень представления в отличие от кода пользовательского интерфейса, запеченного прямо в
- слой сервисной / бизнес-логики как собственный слой
Может потребоваться некоторая дополнительная инфраструктура для обработки вещей, которые мэйнфрейм делал бесплатно
Например, обмен сообщениями, управление контейнером или виртуальной машиной, очереди, интеграция AD / LDAP / OAuth и т. Д.
Так что теперь вы, вероятно, чувствуете, что только что вошли в фармацевтическую рекламу, где я сказал:
«Эта крошечная таблетка решит все ваши боли в спине и желтые проблемы с ногтями на ногах. Потенциальные побочные эффекты могут включать рвоту, кровотечение из глаз и / или ушей, временную потерю зрения, спонтанное облысение и болезненную чувствительность к букве «А» ».
Однако это может быть успешное путешествие, если вы сосредоточитесь на следующем:
- Если у вас большая кодовая база на унаследованных языках / языках мэйнфреймов, этот процесс может очень быстро перевести вашу кодовую базу в более современную кодовую базу.
- С этого момента — ваши команды разработчиков будут гораздо в большей степени способны обновлять приложения в желаемом конечном состоянии, просто благодаря тому факту, что теперь они могут читать код.
Если вы выбираете процесс, который может использовать грамматическое дерево для первоначального преобразования …
Вы можете быстро поворачивать и корректировать обновленный вывод, просто изменив грамматику и запустив ее заново.
Иногда преобразование на основе шаблона является единственным вариантом. Но во многих случаях можно создать дерево грамматики — и тогда вы просто настраиваете свою грамматику вместо одноразовых выходных данных или отдельных шаблонов.
Synhole Tree Transformer Keyhole и его собственный анализатор грамматики COBOL основаны на грамматике и созданы именно для этого!
Это может быть жизнеспособным вариантом, чтобы получить вас в поэтапной реализации …
Особенно, если ваша организация не укомплектована для обработки, возможно, тысяч программ в новый стек.
Преобразовав весь свой унаследованный код за короткое время, вы сможете быстрее избавиться от старых технологий. Затем вы можете перераспределить эти ресурсы для анализа и переписывания или очистки частей кода с наибольшей коммерческой ценностью и ROI.
Это позволяет организации принимать более целенаправленные решения относительно того, что действительно важно для бизнеса.
Предоставляет ценную информацию и анализ бизнес-логики, применяемой в вашей кодовой базе.
В некоторых случаях бизнес-логика может быть такой же старой, как база кода, и больше не применяется. Большинство клиентов находят в этом большую ценность и заканчивают тем, что сокращают свою кодовую базу для преобразования на 10-25% только за счет анализа.
Возможность представить DevOps как часть конверсии.
В зависимости от желаемого конечного состояния кода, возможность представить DevOps как часть преобразования может быть полезной за пределами процесса преобразования. Иногда «необходимость» выдержать какой-либо инструментарий или внедрить новый процесс заканчивается как возможность внедрить передовой опыт, не проходя столько бюрократических проволочек или шлюзов.
Эти новые процессы и инструменты могут быть использованы в других областях бизнеса и повышают эффективность за счет повышения гибкости и некоторых изменений в культуре.
Этот процесс может быть краткосрочным бюджетным выигрышем.
Потенциал для быстрого преобразования и устаревания мэйнфреймов и более старых технологий может привести к возмещению капитальных и эксплуатационных расходов.
Общая стоимость разработки для перевода кода в это преобразованное состояние обычно меньше, чем переписывает команда вручную.
Предостережение с этим элементом заключается в том, что в долгосрочной перспективе это может быть более дорогостоящим мероприятием из-за большого количества кода, теперь используемого в более новых языках и инфраструктурах — могут потребоваться новые / дополнительные ресурсы для поддержки и расширения базы кода. — Но, по крайней мере, вы сможете их найти!
Суть этой стратегии:
Если вы убедитесь, что понимаете, что на самом деле может делать процесс, и выбрали надежный инструмент, основанный на грамматике (например, Keyhole Syntax Tree Transformer и наш анализатор — просто говорите), вы сможете достичь очень предсказуемого результата, который позволит вам сэкономить средства и время. выигрывает.
Теперь, когда мы прошли через определение и плюсы реализации этой стратегии, давайте немного запачкаем руки. Наш сценарий использования этой статьи будет переходить с COBOL на JAVA с использованием нашего преобразователя синтаксического дерева замочной скважины.
«Давай уже перенастроим!»
Чтобы начать этот пример, мы собираемся начать с образца бита COBOL, который был преобразован в синтаксическое дерево JSON нашим проприетарным парсером грамматики. Программа COBOL просто читает хранилище данных DB2 и возвращает список сотрудников. Мы не будем показывать фактическое преобразование COBOL в JSON — вместо этого мы начнем с уже преобразованной программы COBOL.
(Извините, это секретный соус поста в блоге — так что мы собираемся сделать это в стиле кулинарного шоу и начать с индейки, которую мы уже приготовили вчера вечером! Если вы заинтересованы в процессе для вашей организации или хотели бы демонстрацию — пожалуйста свяжитесь с нами ).
Для начала, есть пара пунктов настройки, которые мы должны охватить:
- Вам нужно будет клонировать этот репозиторий для этого примера: https://github.com/in-the-keyhole/khs-syntax-tree-transformer
- Вам нужно будет находиться на машине с поддержкой Docker (Windows 10, различные версии Linux, Mac). Это для примера DB2, если вы не хотите связываться с Docker, в репозитории есть простой пример COBOL.
- Это надуманный пример! Он не предназначен для лечения каких-либо заболеваний или использования в любых производственных условиях! Он предназначен для демонстрации механизма и демонстрации перехода от синтаксического дерева к Java-приложению.
Хорошо, давай к этому!
Шаг первый:
После того, как вы клонировали репо, импортируйте его как проект Maven в Eclipse, STS или Intellij.
Шаг второй:
Выполните метод main с аргументами командной строки для входного файла JSON и сгенерированным именем пакета Java. Вот так:
Это создает испущенную Program.java program
в каталоге проекта:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package khs.res.example.Program public class Program { private Double CONST-PI = null ; private Double WORK- 1 = 0 ; private Double WORK- 2 = 0 ; private Double PRINT-LINE = null ; public void static main(String[] args) { Program job = new Program (); job.A-PARA (); } public void A-PARA () { WORK- 1 = 123.46 WORK- 2 = WORK- 2 + 2 WORK- 2 = WORK- 3 * 3 C-PARA() } public void B-PARA () { CONST-PI = Math.PI; EDT-ID = ZERO } public void C-PARA () { B-PARA() } } |
Ниже приведен входной demo.json
созданный нашим парсером секретного соуса, который будет использовать наша программа:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
|
{ "name" : "Program" , "typeName" : "CLASS" , "variables" : [ { "name" : "CONST-PI" , "typeName" : "VARIABLE" , "value" : null, "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "WORK-1" , "typeName" : "VARIABLE" , "value" : "ZERO" , "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "WORK-2" , "typeName" : "VARIABLE" , "value" : "ZERO" , "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "PRINT-LINE" , "typeName" : "VARIABLE" , "value" : null, "isLocal" : false , "isWorking" : true , "isArray" : true , "fileLevel" : null, "variables" : [ { "name" : "EDT-ID" , "typeName" : "VARIABLE" , "value" : "SPACES" , "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "FILLER" , "typeName" : "VARIABLE" , "value" : "' Perimeter '" , "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "EDT-3-15-CIR" , "typeName" : "VARIABLE" , "value" : null, "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "FILLER" , "typeName" : "VARIABLE" , "value" : "' Radius '" , "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "EDT-3-15-RAD" , "typeName" : "VARIABLE" , "value" : null, "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "FILLER" , "typeName" : "VARIABLE" , "value" : "' Pi '" , "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] }, { "name" : "EDT-1-15-PI" , "typeName" : "VARIABLE" , "value" : null, "isLocal" : false , "isWorking" : true , "isArray" : false , "fileLevel" : null, "variables" : [ ] } ] } ], "functions" : [ { "name" : "A-PARA" , "typeName" : "FUNCTION" , "methods" : [ { "name" : "123.46TOWORK-1" , "typeName" : "METHOD" , "type" : { "name" : null, "typeName" : "MOVE" , "varName" : "WORK-1" , "value" : "123.46" } }, { "name" : "2TOWORK-2" , "typeName" : "METHOD" , "type" : { "typeName" : "ADD" , "value" : "2" , "var1" : "WORK-2" , "var2" : null } }, { "name" : "3GIVINGWORK-3" , "typeName" : "METHOD" , "type" : { "typeName" : "MULTI" , "value" : "3" , "var1" : "WORK-2" , "var2" : "WORK-3" } }, { "name" : "C-PARA" , "typeName" : "METHOD" , "type" : { "name" : "C-PARA" , "typeName" : "CALL" } } ] }, { "name" : "B-PARA" , "typeName" : "FUNCTION" , "methods" : [ { "name" : "PITOCONST-PI" , "typeName" : "METHOD" , "type" : { "name" : null, "typeName" : "MOVE" , "varName" : "CONST-PI" , "value" : "PI" } }, { "name" : "ZEROTOEDT-ID" , "typeName" : "METHOD" , "type" : { "name" : null, "typeName" : "MOVE" , "varName" : "EDT-ID" , "value" : "ZERO" } } ] }, { "name" : "C-PARA" , "typeName" : "FUNCTION" , "methods" : [ { "name" : "B-PARA" , "typeName" : "METHOD" , "type" : { "name" : "B-PARA" , "typeName" : "CALL" } } ] } ] } |
Пример DB2
Теперь, чтобы сделать шаг вперед, мы переводим простые программы DB2 в демонстрационный код Java, который использует DB2 Express.
Вот пример приложения DB2 Cobol:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
* -------------------------------------------------------------- * Selects a single employee into a record 's detail fields, and * then displays them by displaying the record. * * Demonstrates Cobol-to-Java translation of a DB2 SELECT INTO * the detail fields of a parent record. * * Java has no native notion of a record aggregate. A SQL * SELECT INTO similarly lacks a record construct. * * Lou Mauget, January 31, 2017 * -------------------------------------------------------------- IDENTIFICATION DIVISION. PROGRAM-ID. COBOLDB2. DATA DIVISION. WORKING-STORAGE SECTION. EXEC SQL INCLUDE SQLCA END-EXEC. EXEC SQL INCLUDE EMPLOYEE END-EXEC. EXEC SQL BEGIN DECLARE SECTION END-EXEC. 01 WS-EMPLOYEE-RECORD. 05 WS-EMPNO PIC XXXXXX. 05 WS-LAST-NAME PIC XXXXXXXXXXXXXXX. 05 WS-FIRST-NAME PIC XXXXXXXXXXXX. EXEC SQL END DECLARE SECTION END-EXEC. PROCEDURE DIVISION. EXEC SQL SELECT EMPNO, LASTNAME, FIRSTNME INTO :WS-EMPNO, :WS-LAST-NAME, :WS-FIRST-NAME FROM EMPLOYEE WHERE EMPNO=200310 END-EXEC. IF SQLCODE = 0 DISPLAY WS-EMPLOYEE-RECORD ELSE DISPLAY ' Error' END -IF. STOP RUN. |
Это было преобразовано в синтаксическое дерево JSON с помощью нашего анализатора Antlr . Синтаксическое дерево JSON преобразуется в следующее приложение Java с khs.transformer.CommandLine.java
объекта khs.transformer.CommandLine.java
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
/** * Java source, file COBOLDB2.java generated from Cobol source, COBOLDB2.cbl * * @version 0.0.3 * @author Keyhole Software LLC */ public class COBOLDB2 { private static Logger Log = LoggerFactory.getLogger( "COBOLDB2" ); // SQLCA private int sqlcode; // Level 05 private String v_ws_empno; // Level 05 private String v_ws_last_name; // Level 05 private String v_ws_first_name; // Level 01 private InItem[] v_ws_employee_record = new InItem[]{ () -> v_ws_empno, () -> v_ws_last_name, () -> v_ws_first_name }; // Procedure division entry: public static void main(String[] args) { try { COBOLDB2 instance = new COBOLDB2(); instance.m_procdiv(); } catch (Exception e) { e.printStackTrace(); } } private void m_procdiv () throws Exception { final String sql = "SELECT EMPNO, LASTNAME, FIRSTNME FROM EMPLOYEE WHERE EMPNO=200310" ; final OutItem[] into = new OutItem[]{ s -> v_ws_empno = (String)s, s -> v_ws_last_name = (String)s, s -> v_ws_first_name = (String)s }; sqlcode = Database.getInstance().selectInto( sql, into ); if ( sqlcode == 0 ) { Display.display( v_ws_employee_record ); } else { Display.display( "Error" ); } // EXIT ... System.exit( 0 ); } } |
Следующие шаги описывают настройку DB2 для выполнения этого приложения. DB2 Express работает в контейнере Docker. Нет пулов соединений. Это просто демо. ☺
Докер DB2 Express Контейнер
Убедитесь, что у вас есть доступ к Docker .
Используйте этот образ Docker для начальной привязки DB2: https://hub.docker.com/r/ibmcom/db2express-c/
1
2
|
docker run --name db2 -d -it -p 50000 : 50000 -e DB2INST1_PASSWORD=db2inst1-pwd -e LICENSE=accept -v $(pwd)/dbstore:/dbstore ibmcom/db2express-c:latest db2start docker exec -it db2 bash |
Создайте работающий демон-контейнер Docker DB2 Express и войдите в сеанс bash, как показано выше.
Issue su db2inst1
Выпустите db2sampl
(требуется время для создания базы данных «SAMPLE»).
1
2
3
4
5
6
7
8
|
[db2inst1 @6f44040637fc /]$ db2sampl Creating database "SAMPLE" ... Connecting to database "SAMPLE" ... Creating tables and data in schema "DB2INST1" ... Creating tables with XML columns and XML data in schema "DB2INST1" ... 'db2sampl' processing complete. |
При завершении дымовой проверки установки:
Запустите как Java: khs.transformer.CheckDb2Connection
На консоли отображаются следующие данные:
После установки и проверки БД в контейнере Docker мы можем выполнить нашу преобразованную программу Cobol / DB2 в Java — khs.res.db2demo.COBOLDB2.java
. Как только мы выполним эту программу, мы получим следующий вывод:
В основном магия!
Опять же, это придумано, но мы взяли программу на COBOL, которая была преобразована в дерево синтаксиса JSON, а затем получили приложение Java, которое возвращало нам данные из базы данных DB2 — именно то, что сделала программа на COBOL!
В заключении
Надеемся, что после этой статьи и приведенного выше примера мы все лучше разберемся в стратегии Re-Platforming. Является ли эта стратегия правильной для вашей организации — это еще один разговор (который мы хотели бы иметь между прочим — свяжитесь с нами).
Главное, что я хотел поразить, это то, что перенос кода не является «серебряной пулей» для вашего уродливого оборотня, даже если это звучит круто! Я также хотел бы сообщить вам, что, хотя это чревато опасностями, если подходить правильно и с помощью надежного инструмента (например, Keyhole Syntax Tree Transformer и Parse), это может быть очень жизнеспособной стратегией.
«Итак, что мы достигли здесь?»
В итоге мы рассмотрели следующее:
- Краткое введение в модернизацию
- Обзор стратегии Re-Platforming для модернизации
- Пример повторного преобразования с использованием преобразователя дерева синтаксиса Keyhole
- Дополнительные заключительные мысли о ценности / риске этой стратегии
Мы очень надеемся, что вам понравилось так же, как и нам. Пожалуйста, если у вас есть вопросы или пожелания, напишите их ниже или свяжитесь с нами напрямую.
Спасибо, и не забудьте модернизировать ответственно!
Ресурсы / Ссылки: Демонстрацию также можно найти здесь: https://github.com/in-the-keyhole/khs-syntax-tree-transformer
Ссылка: | Приключения в модернизации: стратегия + пример преобразования COBOL в Java от нашего партнера JCG Далласа Монсона в блоге Keyhole Software . |