35P by neo 27일전 | ★ favorite | 댓글 4개
  • 현대 소프트웨어는 지속적 배포(CD)와 자동화된 테스트(CI)를 통해 자주 업데이트되지만, "장기적으로 사용되는 소프트웨어"는 다른 접근이 필요함
    • 예: 원자력 발전소, 비행기, 심장박동기, 선거 시스템 등
      • 신뢰성과 안정성이 중요한 분야에서는 지속적인 변화보다는 안정성과 예측 가능한 변경을 선호

장기 소프트웨어 개발의 핵심 원칙

의존성(Dependencies)

  • 소프트웨어의 의존성은 장기적인 성공에 중요한 요소
  • 소프트웨어는 외부 세계와의 상호작용을 고려해야 하며, 프로그래밍 언어와 같은 기본 선택이 중요함
  • 소프트웨어 의존성 계층 이해
    • 외부 세계: 우리가 통제할 수 없는 클라이언트 소프트웨어(예: 브라우저 등).
    • 기본 선택: 프로그래밍 언어처럼 스택 전체를 재작성해야 변경 가능한 요소.
    • 프레임워크: 코드베이스와 강하게 결합되는 Spring Framework, React 등. 변경은 가능하지만, 매우 높은 비용이 듦.
    • 데이터베이스: 대부분 교체 가능하나 세부적인 조정과 작업이 필요.
    • 헬퍼 라이브러리: 특정 기능을 제공하는 교체 가능한 라이브러리.
  • 시간이 지남에 따라 의존성과 외부 세계는 변화:
    • 의존성의 변화로 인해 코드 수정 또는 행동 변화 발생 가능.
    • 새로운 주요 버전 출시로 호환성 문제 발생.
    • 프로젝트가 중단되거나 사라질 위험.
    • 보안 위험: 의존성이 악성 행위자에 의해 손상될 가능성(npm, PyPI 등).
    • 상업화: 벤처캐피탈(VC)의 새로운 소유자가 유료화.
    • 의존성 간 충돌 문제.
  • 장기적 사용을 고려한 의존성 선택 시 점검해야 할 항목:
    • 기술 수준: 소스 코드를 보고 품질 판단 가능 여부.
    • 사용자 기반: 누가 사용 중인지 확인.
    • 개발 목적: 개발자가 누구이며 목표는 무엇인지 파악.
    • 재정 지원: 자금 지원 여부와 출처.
    • 유지 관리: 보안 릴리스가 주기적으로 이루어지는지 확인.
      • 커뮤니티가 유지 관리를 인수할 가능성.
      • 내가 직접 유지 관리할 수 있는지.
      • 필요 시 재정 지원으로 프로젝트 지속 가능성을 확보해야 하는지.
    • 의존성의 의존성:
      • 하위 의존성의 보안 이력도 검토.
  • 현실적인 접근 방식
    • 의존성 제한:
      • 1600개 이상의 의존성을 가진 프로젝트는 코드가 급격히 변화하며 불안정해질 가능성이 큼.
      • 수많은 의존성을 가진 프로젝트에서는 어떤 코드를 배포하는지 파악조차 어려움.
    • 신중한 추가:
      • 의존성을 추가할 때 기술적 난이도를 부여해 자연스러운 검토 시간을 확보.
      • 장기 프로젝트에서는 필요하지 않은 의존성은 피해야 함.

런타임 의존성(Runtime Dependencies)

  • 이전까지 논의한 내용은 빌드/컴파일 의존성에 국한됨.
  • 그러나 현대 프로젝트는 종종 런타임 의존성도 포함:
    • 예: Amazon S3, Google Firebase.
    • 일부는 사실상 표준처럼 간주됨(S3 등).
    • 그러나 대부분의 런타임 의존성은 특정 서비스에 잠금(Lock-in) 되는 성격이 강함.
  • 10년 후에도 현재 사용 중인 서비스를 대체할 수 있는 대안을 찾는 일은 매우 높은 비용을 초래.
  • 제3자 서비스 의존성 목록은 최소화 또는 비우기가 필요:
    • 특히 클라우드 네이티브(cloud native) 소프트웨어 개발에서는 다수의 고급 제3자 서비스 사용이 일반적.
    • 장기 프로젝트의 경우 이런 의존성은 높은 리스크를 동반.
  • 빌드 타임 서비스 의존성도 중요한 요소:
    • 예: npm install이 더 이상 작동하지 않을 경우, 소프트웨어 빌드 자체가 불가능.
    • 이는 프로젝트의 재사용 가능성을 심각하게 저하시킬 수 있음.
  • 런타임 의존성을 철저히 검토:
    • 잠재적 잠금 문제를 인식하고, 의존성을 줄이거나 제거.
  • 장기적 유지 가능성 확보:
    • 클라우드 또는 제3자 서비스의 대체 가능성을 사전에 고려.

테스트, 테스트, 그리고 테스트

  • 테스트의 필요성은 모두가 동의하는 기본 원칙:
    • 가능한 많은 테스트를 작성.
    • 모든 테스트가 동등하게 가치 있는 것은 아니지만, 테스트 자체를 후회할 일은 거의 없음.
  • 특히 의존성이 많은 프로젝트에서 테스트는 필수:
    • 의존성이 변경되거나 드리프트될 경우, 문제를 조기에 감지하는 데 도움.
  • 테스트의 역할
    • 문제 해결 지원:
      • 변경 상황에 맞춰 빠르게 조정 가능.
    • 리팩토링 지원:
      • 코드 의존성을 제거하거나 변경할 때 자신감을 제공.
    • 장기 유지보수에 유용:
      • 개발이 3년 이상 중단된 이후에도 테스트를 통해 시스템이 여전히 작동하는지 확인.
      • 새 컴파일러, 런타임, 운영체제에서도 기능 유지 여부를 확인.
  • 테스트는 비용이 아니라 투자
    • 더 많은 테스트를 작성:
      • 테스트는 유지보수와 안정성의 기반.
      • 코드 수정 또는 확장 시 테스트는 큰 정신적 지지 역할.

복잡성: 소프트웨어 개발의 최종 보스

  • 복잡성은 소프트웨어 개발의 궁극적 적:
    • 최고의 개발자나 팀도 복잡성에 의해 무너질 수 있음.
    • 엔트로피와 인간 행동의 영향으로 복잡성은 항상 증가.
    • 복잡성을 의식적으로 관리하지 않으면, 프로젝트는 유지 불가능한 상태로 빠질 수 있음.
  • 복잡성과 코드 양의 상관관계
    • 코드 양과 복잡성:
      • 코드가 적을 때는 다소 복잡하더라도 관리 가능.
      • 코드가 늘어날수록 단순성을 유지해야 제어 가능.
      • 관리 가능한 복잡성은 팀의 역량과 "초록 삼각형" 안에 있어야 함.
    • 복잡성의 한계:
      • 팀 인원을 늘리거나 뛰어난 역량의 개발자를 고용하더라도 복잡성 처리에는 한계가 있음.
      • 한계를 넘어서면 프로젝트는 유지보수 불가능한 상태에 빠짐.
  • 코드가 항상 ‘오른쪽 위’로 움직이는 이유:(그래프에서)
    • 더 많은 기능 요청.
    • 불필요한 최적화 시도.
    • 버그 수정 시 기존 복잡성을 줄이는 대신 새로운 코드 추가.
  • 잘못된 API 설계의 비용:
    • 예: CreateFile 함수가 대부분의 경우 파일을 생성하지 않음.
    • 이러한 혼란은 추가적인 인지적 부담과 실수 가능성을 높임.
  • 복잡성 관리 전략
    • 리팩토링은 조기에, 그리고 자주:
      • 불필요한 코드를 제거하고, 단순화에 시간 투자.
    • 테스트에 투자:
      • 테스트가 많을수록 복잡성을 줄이는 작업이 쉬워짐.
    • 복잡성 관리의 중요성:
      • 단순화를 위해 미리 노력하지 않으면, 장기 프로젝트는 결국 "유지보수 불가 상태"로 빠질 위험이 있음.

지루하고 간단한 코드를 작성하라. 그보다 더 간단하게. 그리고 더 지루하게.

"디버깅은 프로그램을 작성하는 것보다 두 배 더 어렵다. 따라서 코드를 작성할 때 최대한 똑똑하게 만들면, 그것을 디버깅할 방법은 무엇인가?" - Brian Kernighan

  • 슈퍼 지루하고 명확한 코드 작성:
    • 나이브(näive)하지만 직관적으로 이해 가능한 코드를 선호.
    • "프리미엄 최적화는 모든 악의 근원이다."
  • 최적화는 반드시 필요할 때만:
    • 너무 간단해서 문제가 될 경우, 나중에 복잡성을 추가하는 건 어렵지 않음.
    • 그 순간이 오지 않을 수도 있음.
  • 복잡한 코드 작성을 지양:
    • 반드시 필요한 시점까지 기다릴 것.
    • 단순한 코드를 작성한 것에 대해 후회할 가능성은 매우 낮음.
  • 고성능 코드나 기능은 특정한 환경에서만 작동할 수 있음.
    • 예:
      • LMDB: PowerDNS에서 안정적으로 사용하기까지 많은 어려움 겪음.
      • RapidJSON: SIMD 가속 JSON 라이브러리. 성능은 뛰어나지만 사용 조건이 까다로움.
  • "나는 이 제약을 극복할 수 있다"는 자신감이 있더라도:
    • 올해는 가능하더라도 5년 후 자신 혹은 후임 개발자는 어려움을 겪을 수 있음.
    • 복잡한 프로그래밍 언어도 동일한 원칙이 적용됨.
  • 결론:
    • 코드를 단순화하라:
      • 정말 간단하게. 그보다 더 간단하게.
    • 최적화는 나중으로 미뤄라:
      • 복잡성은 필요할 때 추가 가능하지만, 초기에 복잡하게 만들면 유지보수가 어려워짐.

LinkedIn 기반의 소프트웨어 개발

  • 현실 vs. 이상
    • 이상적인 접근: 의존성 선택 시 철저한 평가와 검토 필요(위에서 제시한 체크리스트 활용).
    • 현실적인 접근: 때로는 매력적인 기술을 시도해보고, 작동하면 그대로 사용하는 경향.
  • 매력적인 이유
    • LinkedIn의 유명 인사나 인플루언서가 추천한 기술.
    • Hacker News 같은 커뮤니티에서 극찬받는 "최신 프레임워크".
  • 유행 기술은 장기적인 검증이 부족:
    • "10년 이상 유지될 소프트웨어 프로젝트"에는 적합하지 않을 수 있음.
    • 새로운 기술은 초기 단계에서 안정성과 유지보수성 측면에서 문제가 발생할 가능성이 높음.
  • 권장 사항
    • 실험적 영역에서만 사용:
      • 신기술은 먼저 작은 프로젝트나 비핵심 영역에서 시험.
    • Lindy 효과 고려:
      • 기술의 수명은 현재 사용 기간에 비례하는 경향.
      • 오래된 기술일수록 장기적인 안정성을 기대할 수 있음.
  • 신기술은 매력적이지만, 장기적인 프로젝트에는 검증된 안정적인 기술이 더 적합함.

로깅, 텔레메트리, 성능

  • 소프트웨어가 지속적으로 업데이트되거나 배포되지 않을 경우:
    • 웹사이트가 깨졌을 때 즉각적인 피드백을 받지 못할 가능성이 높음.
    • 배포 후 실제 문제 해결까지 시간이 오래 걸릴 수 있음.
  • 초기 릴리스부터 철저한 로깅 및 텔레메트리 구현:
    • 소프트웨어의 성능, 실패, 활동 내역을 기록.
    • 시간이 지나면서 누적된 데이터는 드물게 발생하는 버그 해결에 매우 유용.
  • 부족한 로깅으로 인한 문제:
    • 사용자 UI를 배포했지만 3000개의 폴더를 생성한 사용자가 문제를 보고.
    • 사용자는 "작동하지 않는다"라고만 언급해, 근본 원인 파악에 몇 달 소요.
    • 성능 로깅 및 텔레메트리가 있었다면 문제를 훨씬 빠르게 해결 가능했을 것.
  • 로깅과 텔레메트리는 필수:
    • 소프트웨어 활동을 철저히 모니터링할 수 있도록 설계.
    • 장기적인 배포 및 유지보수 과정에서 예상치 못한 문제 해결에 큰 도움을 줌.

문서화

  • 문서화의 중요성:
    • 단순히 API 문서를 잘 작성하는 것을 넘어 **"왜 이렇게 설계했는지"**를 설명해야 함.
    • 시스템이 어떻게 작동하는지에 대한 아이디어와 철학을 기록.
    • 해결책을 분리한 이유와 비직관적인 설계 결정의 근거를 남겨야 함.
  • 아키텍처 문서 외에 유용한 자료:
    • 내부 블로그 포스트: 개발자들이 시스템 설계에 대한 자유로운 논의 공유.
    • 팀 인터뷰: 설계 결정 배경을 담은 대화 기록.
    • 이러한 문서는 시간이 지나도 팀 내 지식 전수를 가능하게 함.
  • 코드에 주석을 남겨라:
    • "좋은 코드에는 주석이 필요 없다"는 트렌드에도 불구하고, 코드의 '왜'를 설명하는 주석은 필수적.
    • 특정 함수의 존재 이유를 설명하는 내용이 중요.
  • 커밋 메시지 작성:
    • 커밋 메시지는 작업 기록의 핵심. 이를 통해 코드 변경의 이유를 추적할 수 있도록 지원.
    • 사용자들이 쉽게 커밋 메시지를 열람할 수 있는 환경 마련.
  • 문서화를 위한 시간 확보:
    • 개발이 잘 안 되는 날에는 유용한 주석과 기록을 남기는 데 시간을 할애.
    • 팀 차원에서 문서화를 위한 시간을 정기적으로 배정.
  • 왜 그렇게 설계했는지 기록하라:
    • 7년 후 새로운 팀에게 철학과 배경을 전달할 수 있는 자료는 무엇보다 소중함.
  • 주석과 커밋 메시지를 통해 역사를 남겨라:
    • 개발 도중뿐 아니라 장기적인 유지보수를 위한 필수 요소.

팀 구성

  • 팀의 지속성과 소프트웨어의 장기적 성공:
    • 일부 소프트웨어는 80년을 지원하도록 설계됨. 이러한 장기 프로젝트에서는 팀의 유지가 핵심.
    • 현대 개발 환경에서는 평균 3년 정도가 긴 근속 기간으로 간주됨.
    • 좋은 문서화와 테스트는 팀 교체를 어느 정도 보완할 수 있지만, 한계가 있음.
  • 장기 근속의 장점:
    • 10년 이상 팀원 유지:
      • 실제 직원으로 고용하고, 개발자를 잘 관리하는 것이 중요.
      • 장기적인 프로젝트 성공에 핵심적인 "해킹(hack)"으로 간주.
  • 외주 의존의 문제점:
    • 외주 개발자는 코드를 시스템에 넘긴 후 떠나는 경우가 많음.
    • 10년 이상 지속 가능한 소프트웨어 품질을 목표로 한다면 매우 비효율적인 방식.
  • 팀 구성원들이 장기적으로 함께할 수 있는 환경 조성.
  • 외부 컨설턴트 의존을 최소화하고, 내부 팀의 지속 가능성을 높이는 전략이 필요.

오픈 소스를 고려하라

  • 오픈 소스의 장점:
    • 외부 검토를 통해 코드 품질을 유지:
      • 외부의 시선이 개발자들에게 더 높은 기준을 요구함.
    • 더 나은 코드 표준을 유지하기 위한 강력한 메커니즘.
  • 오픈 소스 준비 과정에서의 현실:
    • 기업이나 정부는 종종 오픈 소스화 준비에 수개월에서 수년이 걸린다고 주장.
    • 이유:
      • 내부적으로는 외부에 공개하기 부끄러운 코드 작성이 일반적.
      • 오픈 소스화 전 코드 정리가 필요하기 때문.
  • 적용 가능성 평가:
    • 오픈 소스가 항상 가능한 선택지는 아님.
    • 가능하다면, 코드 품질과 투명성을 높이는 좋은 방법.
  • 오픈 소스는 가능할 때 활용해야 할 중요한 전략.
  • 외부의 시선과 높은 기준은 프로젝트를 올바른 방향으로 유지하는 데 도움이 됨.

의존성 건강 상태 점검

  • 의존성 변화의 문제:
    • 의존성은 시간이 지나면서 기대와 다르게 변화하거나 이탈할 수 있음.
    • 이를 방치하면:
      • 버그 발생
      • 빌드 실패
      • 기타 실망스러운 결과로 이어질 수 있음.
  • 정기적인 건강 점검 권장:
    • 주기적인 의존성 점검:
      • 사전에 문제를 발견할 기회를 제공.
      • 의존성의 새로운 기능을 발견해 코드 단순화나 다른 의존성 제거 가능성도 탐색.
    • 예방적 유지보수의 중요성:
      • 스스로 점검 시간을 계획하지 않으면, 결국 문제가 발생했을 때 강제로 시간을 할애해야 함.
  • 유지보수의 비유:
    • 기계공들의 격언:
      • "유지보수 시간을 스스로 계획하라. 그렇지 않으면 장비가 그 시간을 계획할 것이다."
  • 정기적인 의존성 점검은 장기적인 소프트웨어 안정성과 효율성을 위한 필수적인 활동.
  • 문제를 미리 해결하고, 긍정적인 변화를 발견하는 기회로 활용.

주요 참고 도서

마지막으로

장기적인 소프트웨어 개발을 위한 핵심 권장 사항:

  • 단순함 유지:
    • 단순하게, 그보다 더 단순하게! 필요할 때 복잡성을 추가할 수 있으므로 초기에 지나치게 복잡하게 만들지 말 것.
    • 단순함을 유지하려면 주기적인 리팩토링 및 코드 삭제가 필요.
  • 의존성에 대해 신중히 고민:
    • 의존성은 적을수록 좋음. 면밀히 검토하고 감사.
    • 1600개의 의존성을 감사할 수 없다면, 계획을 재고해야 함.
    • 트렌드나 유행(예: LinkedIn 기반 개발)을 따라가는 선택은 피할 것.
    • 정기적인 의존성 점검: 의존성의 상태를 지속적으로 모니터링.
  • 테스트, 테스트, 그리고 테스트:
    • 변화하는 의존성을 적시에 파악.
    • 리팩토링 시 자신감을 제공하며 단순성을 유지하는 데 도움.
  • 문서화:
    • 코드뿐 아니라 철학, 아이디어, "왜 이렇게 했는지"에 대한 배경까지 문서화.
    • 미래의 팀원들에게 귀중한 자산이 됨.
  • 안정적인 팀 유지:
    • 장기적인 프로젝트 투자를 위해 장기 고용을 고려.
    • 팀 구성원이 오랜 기간 프로젝트에 헌신할 수 있도록 지원.
  • 오픈 소스 고려:
    • 가능하다면 오픈 소스를 통해 더 높은 코드 표준 유지.
  • 로그 및 성능 텔레메트리:
    • 문제를 조기에 파악하고 해결하는 데 중요한 역할.
  • 이 권장 사항들은 새롭지 않을 수 있지만, 경험 많은 개발자들이 강조하는 만큼 깊이 고민해볼 가치가 있음.

이력서 주도 개발이 문제 일까요.

안정성이 중요한 레이어와 속도가 중요한 레이어를 나누고 둘 사이의 관계를 어떻게 처리할지가 가장 중요한 엔지니어링적인 힘.
토스가 안정성만 추구했다면, 여타 은행과 다를게 없겠죠.

위험한건 스페이스 엑스도 그렇구요. 테슬라도 그렇구..

Hacker News 의견
  • 도구 체인을 적극적으로 업데이트하는 것이 개발 과정의 중요한 부분임. 많은 회사들이 도구 체인 업그레이드를 우선순위에서 제외하여 보안 취약점과 같은 문제를 초래함. 최신 컴파일러나 빌드 시스템 릴리스마다 브랜치를 만들어 빌드 상태를 확인하고, 오류가 있으면 버그로 간주하여 즉시 처리함. 이는 코드베이스를 최신 언어 기능으로 점진적으로 현대화하고 리팩토링하는 데 도움을 줌.

  • 서드파티 의존성은 장기적으로 실망스러운 경우가 많음. 새로운 프로젝트에서는 서드파티 의존성을 사용하여 단기적으로 문제를 해결할 수 있지만, 장기적으로는 자체 코드로 대체하는 것이 좋음.

  • 의존성을 벤더링하고 코드 리뷰를 통해 관리하는 것이 필요함. 종종 서드파티 코드의 품질이 낮아 직접 작성하는 것이 더 나은 경우가 많음.

  • Qt, CMake, 현대 C++를 사용하여 장기적인 확장성을 목표로 하는 프로젝트를 진행 중임. 이러한 기술 스택은 지속적으로 기능과 개선을 제공함.

  • Emacs Lisp에서 작업하는 것이 신선한 경험이었음. 라이브러리가 업데이트되지 않아도 안정적으로 작동하는 것이 장점임. Gatsby와 Node를 사용한 경험은 업데이트 문제로 인해 어려움을 겪었음.

  • 단순한 코드를 작성하는 것이 중요함. 복잡한 코드는 필요할 때만 작성하고, 단순한 코드는 후회하지 않음.

  • 시스템과 코드의 문서화가 중요함. 소프트웨어 개발 경험이 많을수록 문서화의 중요성을 깨닫게 됨.

  • 테스트가 계획에서 중요한 역할을 함. NASA의 개발 방식을 참고하여 프로그래밍 오류를 찾는 데 주력해야 함. 의료 소프트웨어 개발에서는 해석을 피하고 동적 메모리 할당을 사용하지 않음.

  • 오래 지속되는 소프트웨어를 작성하는 가장 좋은 방법은 "지루한" 코드를 작성하는 것임. 의존성을 피하고 기본에 충실해야 함.

  • Python에서 의존성 문제로 어려움을 겪은 경험이 있음. 이는 "DLL Hell"로 불리며, COM이 이를 해결하려 했으나 성공적이지 않았음.

  • 산업 소프트웨어에 적용되는 관행은 일반 소프트웨어에 적용하기에는 충분히 견고하지 않음. 엔지니어들은 위험을 완화하려고 하지만, 우리는 위험을 완화하는 데 중점을 둠.