나의 코드가 마지막 필요한 순간까지 제대로 작동되게 하려면 어떻게 유지관리해야 하는가?
소프트웨어 엔지니어링은 프로그래밍과 달리 시간, (규모) 확장, 실전에서의 트레이드 오프를 고려한다.
- 프로그래밍 작업 (개발) : 소프트웨어 엔지니어링 (개발 + 수정 + 유지보수)
- 소프트웨어 엔지니어링에서는...
- 시간의 흐름과 언젠가 변경될 가능성에 주의
- 소프트웨어 자체뿐 아니라 제작하는 조직까지 확장과 효율에 집중
기술과 사업적인 이유에 관계 없이 소프트웨어의 기대 생애동안 요구되는 모든 가치 있는 변경에 대응할 수 있다면 지속가능한 프로젝트라 할 수 있다.
- 시간이 프로그램에 미치는 영향?
- 코드의 예상 수명을 따져보면 알 수 있다 (구동 수명이 아닌 유지보수 수명).
- 짧은 생애를 가진 코드는 시간의 영향을 받지 않지만, 반대는 그러하다 (기반 라이브러리, 운영체제, 하드웨어, 프로그래밍 언어는 쉽게 변경되지 않음).
- 가치가 충분치 않거나 더 중요한 일을 위해 해당 변경을 진행하지 않을 수 있다 (바라는 모습과의 괴리가 기술부채를 의미).
- 하지만 제품이 지향해야하는 길로 나아가는데 대응할 역량 자체가 없다면 지속가능성이 떨어지는 것이라 볼 수 있다 (리스크 ↑).
소프트웨어 엔지니어링은 팀 업무다. 협업은 그 자체로 새로운 문제를 야기하지만, 한 명이 개발하는 것보다 가치있는 시스템을 만들어낼 잠재력 또한 가진다. "반복 수행하는 일들에 비용을 얼마나 쓸 것인가?"
- 팀 조직, 프로젝트 구성, 정책과 관례가 소프트웨어 엔지니어링의 복잡성을 향상시킨다.
- 조직이 성장하고 프로젝트가 확장될수록 소프트웨어 생산 효율도 높아지는가?
- 개발 워크플로의 효율도 우리의 성장에 발맞춰 개선되는가?
- 우리가 따르는 버전 관리 정책과 테스트 전략이 조직 규모가 확장되면 비례하여 비용을 증가시키는가?
소프트웨어 엔지니어 혹은 리더는 지속 가능성을 잃지 않으면서 조직, 제품, 개발 워크플로의 규모를 확장하는 비용을 관리해야 한다.
- 아래와 같은 상황들이 있지만, 앞선 결정을 훗날 다시 검토해야하며, 해당 결정 때문에 생긴 지연 비용을 정확히 계산하여야 한다.
- 불완전한 지표에 기대어 결과에 커다란 영향을 주는 선택을 해야 할 때도 있다.
- 유지보수에 도움이 되는 변경을 연기하거나 확장성이 떨어지는 정책을 받아들여야 할 때도 있다.
소프트웨어의 수명이 프로젝트가 업그레이드 되는 5 ~ 10 년 보다 길다면 외부 환경 변화에 대비해야하기 때문에 업그레이드가 중요해진다.
- 업그레이드를 초기에 고려하지 않은 프로젝트라면 큰 비용을 지불해야한다.
- 프로젝트에서 수행해본 적 없는 작업을 진행해야 한다. 이 과정에서 숨어있던 가정들이 수면 위로 드러난다.
- 업그레이드 담당 엔지니어가 이러한 종류의 경험을 해보지 않았을 가능성이 크다.
- 일반적인 업그레이드보다 작업 규모가 크다. 점진적인 업그레이드가 아니라 수 년 치 업그레이드를 한 번에 진행해야하기 때문이다.
- 업그레이드 여부는 전적으로 업그레이드 비용, 업그레이드가 가져다줄 이익, 프로젝트의 기대 수명에 따라 결정할 수 있다.
- 대규모 업데이트를 성공적으로 마치는 일뿐 아니라 현재 상태를 안정되게 유지할 수 있어야 프로젝트가 오래 지속될 수 있다.
API 사용자가 충분히 많다면 API 명세에 적힌 내용은 중요하지 않다. 시스템에서 눈에 보이는 모든 동작을 누군가는 이용하게 될 것이기 때문이다.
- 최선의 의도, 최고의 엔지니어, 꼼꼼한 코드리뷰가 뒷받침되더라도 공표한 명세나 모범 사례를 완벽하게 구현해냈다고 단정할 수 없는 현실적인 문제가 있다.
- 이 법칙이 적용된다고 해서 계획 세우기를 등한시하거나 소프트웨어를 더 잘 이해하려는 노력을 등한시해서는 안된다. 문제를 제거할 수는 없어도 완화할 수 있기 때문이다.
- 사용자가 늘어나면 무해할 듯한 변경도 일부 사용자의 소프트웨어를 망가뜨릴 수 있다. 따라서 변경이 얼마나 유용할지 분석할 때는 이러한 충돌을 조사, 식별, 해결하는데 따르는 비용도 고려해야 한다.
하드웨어, 언어 런타임, 데이터 구조를 변경할 일 없이 단명할 코드라면 명세되지 않은 사항의 특성에 의존해도 된다. 하지만 코드가 얼마나 생존할지 모르거나 코드가 의존하는 대상이 아무것도 바뀌지 않으리라 확신할 수 없다면 해당 특성이 존재한다고 가정해서는 안 된다.
- 동작한다 vs 옳다
기반 환경을 변경하든, 효율 개선을 하든 변경을 피할 수는 없다. 전혀 변하지 않을 것 같은 것도 새로운 선택지의 도입 효과를 극대화하려면 변경해야하는 경우가 생긴다. 그러므로 장기 프로젝트는 지속가능성에 항상 투자하여야 한다.
변경 비용이 시간 흐름보다 가파르게 상승하는 시스템은 확장가능하지 않다.
- 지속할 수 없는 길로 접어들면 변화가 필요하다.
- 전체 빌드에 걸리는 시간, 저장소에서 내려받는 시간, 프로그래밍 언어 버전 업그레이드 비용과 같은 지표는 천천히 악화한다. 그러므로 관리하여 확장가능하게 만들어야 한다.
- 조직이 10 배로 커지면 이 작업도 10 배로 많아지는가?
- 엔지니어가 해야 할 일의 양이 조직이 커질수록 늘어나는가?
- 코드베이스가 커질수록 작업량도 늘어나는가?
- 이 중 하나에 해당할 경우 작업을 자동화하거나 최적화할 수단이 있는가?
- 사용자가 많아져도 마이그레이션에 대응할 수 있게끔 전문가 조직을 꾸린다.
- 브랜치가 많아질수록 매번 트렁크에 머지, 테스트하느라 반복적으로 시간을 허비하게 된다.
"코드를 짰으면 자기 코드에 대한 테스트도 자기가 제대로 만들었어야지"
- 인프라를 변경하여 서비스가 중단되는 등 문제가 발생하더라도 같은 문제가 CI 시스템의 자동 테스트에서 발견되지 않는다면 인프라 팀의 책임이 아니다.
- 매번 엔지니어를 찾아다니며 문제를 해결할 수 없다.
- 지식을 공유하면 공유 받은 엔지니어의 수만큼 더 나은 지식을 가진 엔지니어가 생겨난다.
- 전문성: 다른 플랫폼에서의 경험 등 여러 방법에 대한 충분한 지식을 쌓는다.
- 안정성: 더 규칙적으로 릴리스하여 릴리스 사이의 변경량을 줄인다.
- 순응
- 익숙함: 업그레이드를 정기적으로 수행하기 때문에 그 과정에서 중복되는 작업을 찾고 자동화하려고 노력한다.
- 정책: 비욘세 규칙과 같은 유용한 정책과 절차를 갖춘다.
- 타임라인 : 개념잡기 - 설계 - 구현 - 리뷰 - 테스트 - 커밋 - 카나리 - 배포
- 타임라인에서 문제 발견 시점을 왼쪽으로 이동 (원점 회귀)시킬수록 수정 비용이 줄어든다.
- 코드 커밋 전에 정적 검사나 코드 리뷰로 찾아낸 버그는 프로덕션 이후에 발견한 버그보다 훨씬 저렴하게 고칠 수있다.
- 금융 비용 (금전 등)
- 리소스 비용 (CPU 시간 등)
- 인적 비용 (엔지니어링 노력 등)
- 거래 비용 (조치를 취하는 비용 등)
- 기회 비용 (조치를 취하지 않는 비용 등)
- 사회적 비용 (선택이 사회 전체에 미치는 영향 등)
이 외에도 현상 유지 편향 (status quo bias)과 손실 회피 (loss aversion) 같은 치우침도 존재한다. 조직의 건실성에는 은행 잔고뿐 아니라 구성원들이 스스로의 가치를 느끼고 생산적인 일을 하고 있다고 생각하는지까지 포함되는데, 소프트웨어 엔지니어링 분야에서는 금융 비용보다 인적 비용이 제한 요소일 가능성이 더 크다. 엔지니어들이 행복을 느끼게 만들고 일에 집중하고 참여할 수 있게 해주면 효율이 높아진다.
좋은 엔지니어링적 결정이란 결국 가용한 모든 근거 자료에 적절한 가중치를 부여하고, 이러한 풍부한 지식을 바탕으로 균형점을 잡는 일이다.
- 엔지니어링 조직의 선택을 결정짓는 요인
- 반드시 해야하는 일 (법적 요구사항, 고객 요구사항)
- 근거에 기반하여 당시 내릴 수 있는 최선의 선택 (적절한 결정권자가 확정)
- 의사결정이 '내가 시켰으니까'가 되지 않게 하자.
근거자료의 가중치를 정하는 시나리오
- 관련한 정량적 데이터를 모두 '측정'할 수 있거나 최소한 '추정'이라도 할 수 있는 경우
- CPU와 네트워크, 금액과 메모리 양 사이의 트레이드 오프를 평가할 때, 혹은 여러 데이터센터에 설치할 CPU N 개를 줄이기 위해 엔지니어를 두 주 정도 투입할지를 고민할 때
- 측정하기 어렵거나 측정 방법을 모르는 경우
- 엔지니어의 시간이 얼마나 들지 모르겠다. 엉망으로 설계된 API의 엔지니어링 비용을 측정할 방법은? 선택한 제품의 사회적 영향은?
1은 결함이 파고들 여지가 적다. 비교 가능한 요소들을 모두 고려하여 결정. 2는 쉬운 답이 나오기 어렵다. 절충안을 찾기 위해 경험, 리더십, 선례에 기대야하지만 이런 결정에도 똑같은 우선순위와 관심을 두어야 한다.
분산 빌드 시스템 구축으로 엔지니어 생산성을 향상시켰으나, 엔지니어들이 빌드를 최적화할 필요성을 느끼지 않게되어 분산 빌드 시스템의 유지보수 비용이 더 커졌다. 이로인해 목표와 제약을 다시 정하고, 모범사례 (의존성 최소화, 의존성 관리 주체를 머신으로 변경)를 만들고, 새로운 생태계를 위한 도구와 유지보수에도 투자해야 한다는 것을 알았다.
- 포크
- 프로젝트의 수명이 짧거나 포크한 코드의 영향 범위가 제한적이라면 유리함.
- 장점
- 당면한 문제에만 최적화한 해법이 범용적인 해법보다 성능이 뛰어날 가능성이 크다.
- 외부 솔루션에 의존하지 않고 온전히 내가 통제할 수 있다.
- 단점
- 확장성과 지속가능성에 문제가 생길 수 있다. 예를들어 보안문제가 발생하면 모든 포크를 찾아다니며 문제를 발견하고 수정해야 한다.
의사결정을 내리는 근거인 데이터는 취합하는 시점에 따라 달라질 수 있다. 따라서 과거의 결정이 향후 나타날 데이터 상으로 잘못된 결정일 수 있다. 이 때 잘못된 결정을 인정하고 방향을 수정하는 것이 옳다.
- 시스템의 생애동안 과거에 내린 결정을 수시로 재고해봐야 한다.
- 잘못을 인정하는 리더가 더 존경받는다.
- 근거 (데이터)에 기초해 결정하되 측정할 수 없는 요인들에도 가치가 있음을 명심해야 한다. 이는 리더의 덕목이다.
무엇이 우월하다는 것이 아니라 각각 적용되는 제약 사항, 가치, 모범 사례가 다르다는 것이다.
- 단명하는 프로젝트에서 통합 테스트, CD, Semantic versioning, 의존성 관리에 목멜 필요는 없다. 주어진 과업을 쉽게 해결할 수 있고 당장 활용할 수 있는 방법을 찾는 것이 더 적합한 방향이다.
- 둘 간의 차이는 시간 흐름에 따른 코드 관리, 시간 흐름에 따른 규모 확장의 영향, 이런 관점에서의 의사결정 방식에 있다.
- 소프트웨어 엔지니어링은 활용 가치가 남아 있는 한 오랫동안 코드를 유용하게 관리하고 팀 간 협업을 가능케 하는 정책, 관례, 도구 모두를 아우르는 종합적인 개념이다.
- 프로그래밍: 코드를 생산하는 작업
- 소프트웨어 엔지니어링: 프로그래밍에서 확장하여 소프트웨어의 수명이 다할 때까지 코드를 유지보수하는 문제를 고려
- 코드의 기대 수명동안 의존성, 기술, 제품 요구사항 변경에 대응할 역량이 갖춰져야 지속 가능한 소프트웨어이다. 변경하지 않기로 결정하더라도 변경할 수 있는 역량 자체는 가지고 있어야 한다.
- 조직에서 반복적으로 수행하는 모든 작업은 필요 인력 측면에서 확장 가능 (선형 증가 혹은 그 이하) 해야 한다.
- 전문성은 규모의 경제와 결합될 때, 즉 조직이 커질수록 효과가 커진다.
- 의사결정을 데이터 주도로하는 것은 좋은 방향이다. 하지만 현실에서의 의사결정은 데이터, 가정, 선례, 논의를 종합하여 이루어진다. 고려 요소 대부분에 객관적인 데이터가 주어지면 가장 좋겠지만 순전히 데이터만으로 결정되는 경우는 드물다.
- 데이터 주도 방식은 시간이 흘러 데이터가 변하면 (혹은 가정이 무너지면) 프로젝트의 방향도 바뀔 수 있음을 뜻한다. 잘못을 인정하고 계획을 수정하는 것은 당연한 일이다.