실제 제작 소스에서 찾은 질문을 독립 합성 코드로 다시 만들었다. 전체 구현과 실행 예제, 테스트를 함께 공개하며 원래 업체의 프로그램과 자료는 연결하지 않는다.

세 값이 같은 상태가 되지 않게 한다

현재 입력, 전달받은 저장값과 예시 출처는 별도다. open_state는 예시가 있으면 예시를 보여 주되 저장 초안은 남긴다. 저장값은 명시적으로 불러올 때만 현재 입력이 된다. 실제 운송 도구의 예시 우선과 초안 제안 구조를 읽고 어떤 데이터가 화면을 채웠는지 설명할 수 있는 계약으로 분리했다.

이 함수는 localStorage를 읽거나 브라우저의 탭을 제어하지 않는다. 호출자가 넘긴 상태를 복사해 새 상태를 반환한다. 실제 기록을 삭제하거나 다른 기기와 동기화하지 않으므로 화면의 저장 안내와 서버의 지속 저장을 같은 보장으로 읽으면 안 된다.

교체 전에 로컬 번호를 확인한다

불러오기와 프리셋 적용, 현재 초기화는 expected_revision을 확인한다. 편집 이후 오래된 번호의 동작은 실패한다. 저장값 제거와 현재 초기화도 다르며 어느 쪽을 비워도 다른 쪽을 자동으로 지우지 않는다. 새 예제에 추가한 정책이며 원래 업체 운영 화면에 설치하지 않았다.

이 번호는 한 문서 안의 순서다. 인증, 서버 잠금과 여러 사용자 사이의 충돌을 해결하지 않는다. 그런 기능은 기존 업무 모듈과 별도 통합에서 다뤄야 한다. 초안을 보존하는 함수가 제공하는 범위를 작게 유지했다.

초안 형식과 측정 검증은 다르다

저장 형식은 version 1과 distance·liters의 제한된 텍스트만 받는다. 예시 쿼리는 허용한 preset 하나만 받고 중복·알 수 없는 키는 거절한다. 문자열이 유효한 초안이라고 실제 거리와 연료가 유효한 것은 아니다. 계산 전에는 단위와 숫자 범위를 별도 검사해야 한다.

전체 코드는 아래에 공개한다. 입력을 그대로 바꾸지 않는 작은 함수들이므로 상태 전후를 비교하기 쉽다. 허용 필드를 늘릴 때는 개인정보가 섞이는지와 이전 버전을 읽는 정책을 먼저 정해야 한다. 버전 숫자만 올리는 것이 호환성 설계의 전부는 아니다.

코드와 구현 살펴보기

독립 합성 전체 구현

"""Synthetic draft-origin state only. No browser storage, server or URL navigation."""
from copy import deepcopy
from urllib.parse import parse_qsl
PRESETS={'example-v1':{'distance':'100','liters':'20'}}
def fields(value):
    if not isinstance(value,dict) or set(value)!={'distance','liters'}:raise ValueError('known draft fields')
    if any(not isinstance(v,str) or len(v)>40 for v in value.values()):raise ValueError('bounded draft text')
    return dict(value)
def import_stored(value):
    if value is None:return None
    if not isinstance(value,dict) or set(value)!={'version','inputs'} or type(value['version']) is not int or value['version']!=1:raise ValueError('storage version')
    return {'version':1,'inputs':fields(value['inputs'])}
def query_preset(query):
    if not isinstance(query,str) or len(query)>200:raise ValueError('bounded query')
    values=parse_qsl(query.lstrip('?'),keep_blank_values=True,strict_parsing=True)
    if len(values)>1 or any(k!='preset' for k,v in values):raise ValueError('known query only')
    if not values:return None
    name=values[0][1]
    if name not in PRESETS:raise ValueError('unknown preset')
    return name
def open_state(preset=None,stored=None):
    stored=import_stored(stored)
    if preset is not None and preset not in PRESETS:raise ValueError('unknown preset')
    return {'revision':0,'current':deepcopy(PRESETS[preset]) if preset else {'distance':'','liters':''},'origin':'example' if preset else 'blank','stored':deepcopy(stored)}
def require_revision(state,expected):
    if type(expected) is not int or expected!=state['revision']:raise ValueError('input changed')
def edit(state,field,text):
    if field not in ('distance','liters') or not isinstance(text,str) or len(text)>40:raise ValueError('edit')
    out=deepcopy(state);out['current'][field]=text;out['revision']+=1;out['origin']='edited';return out
def save_draft(state):
    out=deepcopy(state);out['stored']={'version':1,'inputs':fields(state['current'])};return out
def load_saved(state,expected):
    require_revision(state,expected)
    if state['stored'] is None:raise ValueError('no saved draft')
    out=deepcopy(state);out['current']=fields(import_stored(state['stored'])['inputs']);out['revision']+=1;out['origin']='saved';return out
def clear_saved(state):
    out=deepcopy(state);out['stored']=None;return out
def reset_current(state,expected):
    require_revision(state,expected);out=deepcopy(state);out.update(current={'distance':'','liters':''},origin='blank',revision=state['revision']+1);return out
def apply_preset(state,preset,expected):
    require_revision(state,expected)
    if preset not in PRESETS:raise ValueError('unknown preset')
    out=deepcopy(state);out.update(current=deepcopy(PRESETS[preset]),origin='example',revision=state['revision']+1);return out

두 종류의 초기화를 검산한다

현재에 예시 A가 있고 저장에 초안 B가 있는 상태에서 저장 제거를 실행하면 A는 남는다. 같은 시작 상태에서 현재 초기화를 실행하면 B는 남는다. 두 결과를 따로 기록해야 제거 버튼의 이름도 정확해진다. 이 반례가 저장값과 화면값을 한 변수로 합치지 않은 이유다.

이 반례는 실행 기록과 테스트로 다시 확인할 수 있다. 다른 업무에 적용할 때는 이름만 바꾸지 말고 입력의 의미와 실패 정책이 같은지 먼저 확인해야 한다. 작은 독립 함수는 운영 프로그램 전체를 대신하지 않는다.

공개 코드와 ZIP은 같은 검토 파일을 사용한다. 실제 업체의 자료를 가져오거나 외부 시스템을 실행하지 않은 상태에서도 핵심 판단을 확인할 수 있도록 만들었다. 새로운 연결을 추가하면 그 연결의 결과는 별도로 기록해야 한다.

직접 확인한 실행과 남겨 둔 경계

소스 폴더와 ZIP을 새로 푼 폴더에서 각각 14개 테스트가 통과했다. 실행 예제의 실제 반환값도 records.json으로 남겼다. 공개 페이지의 코드는 내려받기 구현과 대조할 수 있다. 과거 프로젝트의 완료 보고와 테스트 수치를 이번 실행의 증거로 가져오지 않았다.

아래 선택기는 실제 로컬 실행에서 저장한 결과를 읽는다. Python이나 서버를 브라우저에서 실행하는 기능은 아니다. 이 검수는 실제 브라우저의 렌더링과 물리적 기기 조작을 포함하지 않는다. 실서비스 통합의 결과는 별도 근거로 확인해야 한다.

저장된 실행 기록을 읽는 뷰어입니다. Python이나 서버를 브라우저에서 실행하지 않습니다.

내려받아 적용하기 전에

ZIP에는 전체 코어와 테스트, 실행기, 저장된 결과와 사용 안내가 있다. 라이브 예제는 두 언어 HTML도 포함한다. 수정과 재사용이 가능한 새 합성 코드이며 고객 원본 이미지, 실데이터와 계정 정보는 없다. 실제 프로그램을 실행하는 지시와 외부 전송도 넣지 않았다.

적용할 때는 먼저 단위와 입력 출처, 실패한 경우의 안내를 실제 업무에 맞춰 정해야 한다. 테스트 통과가 모든 입력 사실과 운영 권한을 보장하지는 않는다. 필요한 인증·저장·전송과 화면 검수는 작은 계약 바깥의 일로 남긴다. 나는 재사용할 코드와 함께 어떤 판단을 다시 해야 하는지도 읽을 수 있게 공개한다.

구현·테스트·실행 예제 ZIP 받기
독립 합성 코드, 수정·재사용 가능