
Java 기반 시스템을 PHP로 이식하는 프로젝트 기록: AI 활용으로 초고속 캐치업
요약
Java 기반의 B2B 업무 시스템을 PHP로 이식하는 과정에서 Claude를 활용해 코드의 의도를 해독하고 로직의 등가성을 검증한 프로젝트 기록입니다. 사양서가 불분명한 상황에서 AI를 통해 구 시스템의 복잡한 로직을 정확히 파악하고 재현하는 방법을 다룹니다.
핵심 포인트
- 사양서 대신 구 시스템 코드를 정답으로 삼는 '직역' 방식의 이식 전략
- Claude를 활용한 Java 코드의 비즈니스 로직 및 구현 의도 해독
- Java와 PHP 간의 논리적 등가성 검증을 통한 코드 리뷰 효율화
- 도메인 지식이 부족한 상태에서 AI를 개발 보조 도구로 활용하는 사례
안녕하세요! 주식회사 무쿠이루(Mukuiru)의 iguchi입니다.
1. 서론
나의 포지션: 「사양을 모르는 이식 담당자」
업무 사양·요건: 벤더(패키지 제공처)가 보유·관리 -
나의 역할: 「구 시스템(Java)과 동일하게 동작하는 PHP 버전 제작」 개발자
즉, 「무엇을 만들어야 하는가」는 벤더가 정의한 구 시스템의 코드에 적혀 있습니다. 나는 그것을 읽고 PHP로 재현합니다. 설계 판단이 아니라, 해독과 재현이 업무입니다.
이것이 이 프로젝트의 가장 큰 특징이자, 가장 큰 어려움이기도 했습니다.
시스템 개요
대상은 도매·유통업을 위한 수주·발주·재고·매출 관리를 담당하는 B2B 기간 업무 시스템입니다. 수주부터 출하·매출 계상·장표 출력까지, 상류(商流)의 거의 전 영역을 커버하는 올인원 업무 패키지로, 가동 화면 수는 100개를 넘습니다.
구 시스템은 Java로 구축되어 있으며, 오랜 가동 실적을 가지고 있습니다. 이번에 그 로직을 그대로 이어받은 PHP 제 신규 시스템을 구축하게 되었습니다. 언어 선정 경위는 제 담당 범위 밖이므로 본고에서는 다루지 않겠습니다.
이식 방침: 「직역」을 관철
설계 방침은 처음부터 정해져 있었습니다——「구 시스템을 정답으로 삼아, 충실하게 이식(직역)한다」.
사양서보다 실제로 돌아가고 있는 구 시스템이 더 신뢰할 수 있다는 현장의 현실이 있습니다. 업무 담당자가 「이전과 똑같이 동작한다」고 확인할 수 있는 것이 최우선입니다. 코드를 깔끔하게 만들고 싶은 충동은 봉인하고, 구 구현을 1차 소스로 취급했습니다.
2. 「사양을 모르는」 상태로 이식하는 어려움
2-1. 코드가 유일한 사양서
업무 도메인에 정통한 것은 벤더 측입니다. 내가 참조할 수 있는 것은:
- 구 시스템의 Java 코드
- 단편적인 설계서 (반드시 최신은 아님)
- 업무 담당자에 대한 히어링
이런 상황에서 100개 이상의 화면을 가진 시스템을 이식할 때, 「이 코드는 무엇을 하고 있는가」를 스스로 해독해야 하는 상황이 반복해서 찾아옵니다.
// 이런 코드를 마주함
if (numericConv(qty * unitPrice, 17, 0) != numericConv(billed, 17, 0)) {
// ...어떠한 체크를 하고 있다...
...
numericConv란 무엇인가. 왜 17자리인가. 벤더에게 물어보면 알 수도 있겠지만, 매번 물어볼 수는 없는 노릇입니다.
2-2. 여기서 AI가 무기가 되었다
이 프로젝트에서 가장 효과적이었던 개발자의 무기는, AI (Claude)를 통한 코드 해독과 신구 비교입니다.
① Java 코드의 의도를 읽어내기
컨텍스트(Context)를 가지지 않은 Java 코드를 그대로 AI에 붙여넣고, 「이 코드는 무엇을 하고 있는지, 왜 이런 구현인지 고찰해 달라」고 물으면, 놀라울 정도로 정확한 해석이 돌아옵니다.
입력: numericConv의 Java 구현 코드
AI의 답변:
「이 메서드는 BigDecimal이 아니라 double 부동 소수점 연산으로 반올림을 수행하고 있습니다.
...
이를 보고 PHP 측에서도 round()가 아니라 동일한 알고리즘을 이식해야 함을 확신할 수 있었습니다.
② 신구 코드의 논리적 등가성 검증
Java와 PHP의 구현이 「동일하게 동작하는가」를 확인할 때, 육안 확인에는 한계가 있습니다. AI에 양쪽 코드를 모두 전달하여 「이 Java와 이 PHP가 같은 처리를 하고 있다고 말할 수 있는가, 차이가 있다면 지적해 달라」고 물으면, 변수명이나 언어의 구문 차이를 넘어 로직의 차이를 지적해 줍니다.
입력: Java의 otherSalesInsert()와 PHP의 otherSalesInsert()를 나란히 붙여넣기
AI의 답변:
「Java 버전에서는 agencyCd를 세팅한 후 null 체크가 있습니다,
...
코드 리뷰에서 「구 시스템의 대응 처리와 나란히 확인하는」 작업을 AI가 대폭 가속해 줍니다.
③ 「왜 이렇게 작성했는지 알 수 없는 코드」의 해석
시스템에는 「불필요해 보이지만 사실은 의미가 있는」 코드가 심어져 있습니다. 주석이 남아 있지 않은 경우도 많습니다. AI에 해당 코드의 컨텍스트를 전달하면, 「이 패턴은 〇〇의 경우를 대비한 방어 코드(Defensive code)가 아닌가」라는 가설을 제시해 줍니다. 그것을 바탕으로 업무 담당자에게 타겟을 좁힌 확인을 할 수 있다는 점이 특히 유효했습니다.
3. 기술적인 도전과 대책
3-1. 부동 소수점(Floating Point)의 함정: 구 구현을 해독하고 나서야 알게 된 것
기반 시스템에서 수치 계산을 다룰 때, **반올림 오차 (Rounding Error)**는 치명적입니다. 재고 수량이나 금액이 단 1엔, 1개라도 어긋나면 그대로 장부의 오류가 됩니다.
구 시스템에서는 BigDecimal을 사용하고 있을 것이라고 막연히 생각했습니다. 하지만 AI의 도움을 받아 소스 코드를 읽어보니, 독자적인 부동 소수점 반올림 알고리즘이 사용되고 있다는 사실이 판명되었습니다.
// Java의 구 구현 (개념)
public static String numericConv(double value, int scale) {
// 「절대값에 0.5×10^-scale을 더해서 버림」이라는 독자적인 구현
...
언뜻 보기에는 HALF_UP 처럼 보이지만, double의 부동 소수점 오차가 개입되면 동작이 달라집니다.
// 실제로 발견한 차이
-1100.25 → 구 시스템: -1100.2 / HALF_UP: -1100.3 ← 어긋남
100.25 → 구 시스템: 100.2 / HALF_UP: 100.3 ← 어긋남
...
number_format(), round(), bcscale() 중 그 어떤 것을 사용해도 완전히 일치하지 않았습니다. 해결책은 구 구현의 알고리즘을 그대로 PHP로 이식하는 것뿐이었습니다.
// PHP에서의 재현 구현 (개념)
function numericConvUpc(float $value, int $precision, int $scale): string {
// Java의 독자적인 float 알고리즘을 PHP에서 재현
...
교훈: 「어차피 BigDecimal이겠지」라는 선입견은 위험합니다. AI로 알고리즘을 해석한 뒤에 이식에 착수하는 습관이 중요했습니다.
3-2. 타입 시스템의 차이에 대처하는 법
Java는 컴파일 시점에 타입 에러를 검출할 수 있습니다. PHP는 동적 타입 언어(Dynamic Typing)이지만, PHP 8.x에서는 다음 조합을 통해 상당한 타입 안정성 (Type Safety)을 확보할 수 있습니다.
| 대책 | 내용 |
|---|---|
declare(strict_types=1) | 암시적 타입 캐스팅 (Type Casting) 금지 |
| 타입 선언 철저 | 인수, 반환값, 프로퍼티에 타입을 명시 |
| PHPStan Lv6~8 | 정적 분석 (Static Analysis)을 통한 컴파일 시점 상당의 체크 |
| IDE 지원 | PhpStorm을 통한 타입 추론 및 보완 |
Java의 null 안전성 (Optional이나 @NonNull 어노테이션)에 익숙한 사람에게 PHP의 null 처리는 처음에는 두렵게 느껴집니다. ?? 연산자나 null 체크 작성 방식을 통일하는 코딩 규약 정비가 필요했습니다.
3-3. 코드 자동 생성 파이프라인: 「md가 정본」이라는 운영 규칙과의 사투
신규 시스템에는 Markdown 사양서로부터 코드를 자동 생성하는 파이프라인이 존재합니다.
사양서 (Markdown)
↓ md_to_excel.php
Excel 사양 시트
...
이 파이프라인을 처음 접했을 때, 가장 먼저 빠졌던 함정이 이것입니다.
❌ 실수한 것: 생성된 후의 PHP 파일을 직접 수정 → 재생성 시 사라짐
✅ 올바른 순서: sqlspec_md/*.md를 수정 → 재생성
「md가 정본이며, 생성물을 직접 수정해서는 안 된다」라는 규칙은 구두로 공유되었지만, 직접 경험해 보기 전까지는 제대로 이해하지 못했습니다.
하지만 익숙해지면 이 메커니즘은 매우 강력합니다. 컬럼 추가, WHERE 조건 변경, 바인드 변수 순서 변경이 모두 Markdown 수정만으로 완결되며, PHP 파일에는 손을 댈 필요가 없습니다. 이식 작업에서는 「SQL 사양 변경을 구 시스템에서 읽어내어 md에 반영하는」 작업이 주가 됩니다.
3-4. 「이식」과 「개선」의 경계선을 지키기
PHP로 다시 작성하는 과정에서 「여기는 더 깔끔하게 쓸 수 있는데」라는 충동이 몇 번이고 찾아옵니다.
예를 들어, Java 코드에서 불필요하게 길어 보이는 null 체크가 연속되는 장면—PHP의 ?? 연산자를 사용하면 한 줄로 합칠 수 있습니다. 하지만, 그렇게 해서는 안 됩니다.
이유는 구 구현의 「장황함」 그 자체가 사양의 일부일 수도 있기 때문입니다. 안이한 리팩터링 (Refactoring)은 검증 비용의 증가로 직결됩니다.
규칙은 단순합니다: 명시적으로 승인된 차이 이외에는 구 구현에 충실하게 작성한다.
이 규칙을 지키기 위해서도 AI를 통한 신구(新舊) 비교가 효과적입니다. "리팩터링(Refactoring)하고 싶어진 부분"을 AI에게 "이 Java와 이 PHP는 동일하게 동작하는가"라고 확인하는 습관을 가짐으로써, 의도하지 않은 사양 변경을 방지할 수 있었습니다.
4. 이식을 통해 알게 된 점
좋았던 점
컴파일→배포 사이클이 사라짐
코드를 작성하자마자 바로 브라우저에서 확인할 수 있습니다. "잠깐 테스트해 보는" 비용이 낮아져, 이식 검증 작업이 격식 없이 빨라졌습니다.
모던(Modern) PHP의 타입 지원이 생각보다 유용함
"PHP는 타입이 없는 언어"라는 말은 과거의 이야기입니다. PHP 8.1의 enum이나 8.2의 Readonly Properties 등, Java 엔지니어가 익숙하게 사용하는 개념들을 PHP에서도 자연스럽게 작성할 수 있습니다.
힘들었던 점
암묵적인 사양의 발굴
"왜 이렇게 작성했는지 알 수 없는 코드"가 구 시스템에 매립되어 있습니다. 불필요해 보이지만 실제로는 특정 데이터 패턴에 대한 대응이었거나, 당시 담당자만이 이해할 수 있는 주석이 달려 있는 경우 등입니다. AI의 해석 지원이 이 지점에서 크게 효과를 발휘했지만, 최종적으로는 업무 담당자에게 확인하는 것이 필수적인 상황도 많았습니다.
"사양은 벤더(Vendor)에 맡긴다"의 어려움
동작 판단에 고민이 생겼을 때, "이것이 맞는지"를 확인할 수 있는 1차 정보가 구 시스템의 코드밖에 없습니다. 그 코드를 정확하게 읽어내야 하는 책임이 개발자인 자신에게 모두 지워집니다. 그것이 이식 담당자가 느끼는 본질적인 고충이라고 생각합니다.
5. 개발자로서 느낀 점
이 프로젝트는 설계가 아니라 "해독과 재현"의 업무입니다. 업무 로직의 정확성은 벤더가 보증하고, 자신은 그것을 PHP로 충실히 재현합니다.
언뜻 보기에는 수수한 작업처럼 들릴 수 있지만, 실제로는:
- 언어를 초월하여 로직의 등가성(Equivalence)을 보증하는 책임
- 암묵지(Tacit knowledge)를 코드로부터 읽어내는 능력
- "개선하고 싶은 충동"을 억제하고 직역을 관철하는 자제심
이라는, 순수하게 기술적인 능력이 요구되는 일이었습니다.
그리고 AI는 이 "해독과 검증"이라는 작업에서 단순한 보조 도구를 넘어선 존재가 되어 있습니다. Java의 습성을 읽어내고, 신구 차이점을 지적하며, "왜 이렇게 되어 있는가"를 가설로서 제시해 주는 것——. 이식 담당자에게 AI는 코드를 작성하기 위한 도구가 아니라, 구 시스템을 이해하기 위한 대화 상대로서 기능하고 있습니다.
마찬가지로 "기존 시스템의 충실한 이식"에 도전하는 누군가에게 참고가 되기를 바랍니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기