코드 추가에 대한 두려움 증상들과 이를 어떻게 극복 할 수 있는지에 대해 살펴보자.
불확실성 받아들이기: 위험감수
소프트웨어 개발이란 도메인을 학습하고 그 지식을 프로그래밍 언어로 코드화 하는 것이다.
지식을 구축하는 효과적인 방법은 실험이지만 실험에는 용기가 필요하다.
팀 생산성의 가장 큰 예측변수는 심리적 안정, 즉 팀원들이 서로를 신뢰하고 위험을 감수하는 것이 안전하다고 느끼는지 여부다.
불확실한 영역을 두려워할 때가 많지만 그 영역이 바로 우리가 배워야 하는 부분이다.
"고통스러울수록 더 시도하라"
두려움 극복을 위한 스파이크 사용
실패에 대한 두려움은 생산성을 가로막는데 이는 스파이크로 극복 할 수 있다.
스파이크란, 프로젝트 초기에 스토리를 진행하기 앞서 사용할 기술(라이브러리, 프레임워크)을 조사하고, 요구사항에 관련된 배경지식을 습득하는 단계이다. 스파이크를 통해 앞으로 진행할 스토리의 속도를 쉽게 추측할 수 있다.
(참고) https://qwefgh90.github.io/sphinx/design/design_agile.html
- 스파이크 동안 생성된 코드는 메인으로 들어가지 않기 때문에 결함이 있는지 여부는 중요하지 않다.
- 스파이크는 자신감과 함께 첫 번째 실제 버전을 더 좋게 만드는 데 사용할 수 있는 지식을 제공한다.
- 이런 지식 전파를 위해 문서화 하는 형식으로 결과를 체계화 한다.
(폐기될 코드 작성을 권장하고 장려하는 문화가 필요하다.)
※주의※
- 스파이크로는 가설, 실험 및 사용자 친화성을 테스트 한다.
- 스파이크를 하는 동안은 코드를 더 좋게 만들려고 하지 않아야 한다.
낭비나 위험에 대한 두려움 극복을 위한 사용 시간 비율 지정
개발 도구와 파이프라인이 실제 프로덕션 코드보다 훨씬 더 정교한 경우 두려움이 생긴다.
비즈니스 로직을 작성하기도 전에 테스트 환경, 분기전략, 저장소 구조, 기능전환 시스템, 빌드 설정등에 대한 막대한 시간을 소비하는 경우가 있는데 이는 위험이나 낭비를 줄이는 데 사용해야 한다.
지원도구의 유지보수 및 개발과 같은 비기능 요구사항에 개발자 시간의 20%를 할당 하는 것을 권장한다.
-> 중요한 유지보수 작업이 특정 작업으로 무시되지 않게 된다.
-> 복잡성의 비율을 낮출 수 있다. (20%의 비율은 그 기능이 프로덕션 코드보다 더 복잡해 질 수 없다는 것을 의미한다.)
20%의 할당시작을 적용하는 효율적인 방식
이해 관계자의 요청에 의해 발생되지 않는 모든 작업들인 비티켓 작업들을 금요일에 몰아서 한다.
- 하루 정도면 중요한 작업을 수행하기에 충분한 시간이다
- 일상적인 티켓 작업에 활력을 불어 넣을만한 리프레쉬 할 수 있는 업무이기도 하다.
불완전성에 대한 두려움 극복을 위한 점진적 개선
가면 증후군은 스스로를 자격이 없는 사람으로 간주하여 누군가 자신을 사기꾼으로 폭로할까봐 두려워하는 것이다
개발자는 다른사람의 코드를 경솔하게 비판한다. 이 때 우리는 내 코드에 대해 누군가 그런말을 하고 있을 까 걱정한다.
그리고 완벽한 코드를 위해 질질 끌거나 사소한 작업에 대부분의 시간을 보낸다.
✓ 코드를 효율적으로 만들려면 스킬과 프로파일링이 필요하다.
✓ 사용하기 쉽게 만들려면 테스트와 실험이 필요하다.
✓ 쉽게 확장하려면 리팩터링과 선견지명이 필요하다.
✓ 안정적인 코드를 만들려면 테스트나 정확한 데이터 타입의 사용이 필요하다.
이 모든것에는 '시간'이 소요된다.
하지만 '생산 비용' 역시 중요하다.
무엇에 초점을 맞출 건인지 어디서 불완전함을 받아들일 것인지 선택해야 한다.
코드 작성시 고려할 지표가 너무 많고 한번에 모든 지표를 최적화 할 수 없다.
개발자의 삶에 최적화를 시킨다. = 일을 받는 순간부터 작업을 시작하기까지의 시간을 최대한 짧게 만드는 것
-> 테스트, 테스터, 이해관계자 및 사용자로부터 피드백을 받을 때까지 실습을 최대화하고 피드백까지의 시간을 최소화 할 수 있다.
-> 피드백 루프가 짧으면 빨리 개선시켜 품질 향상을 도모할 수 있다.
복사 및 붙여넣기가 속도에 미치는 영향
코드 복제에는 고려해야 할 두가지 중요한 특성이 있다.
1. 코드를 공유하면 코드가 사용되는 모든 위치에 영향을 미치기 쉽다.
- 공유 코드는 동작에 대한 전역적인 변경을 빠르게 할 수 있다.
- 공유 코드는 여러 호출자들 중 특정 호출자에 대해서만 동작을 변경하는 것은 어렵다.
- 복제한 코드는 코드를 호출하는 각 호출자마다 분리해서 변경하기 쉽다.
- 복제한 코드는 수정이 필요한 경우 모든 위치에서 수정해야 하기 때문에 공통 작업을 수행하기는 어렵다.
코드를 공유하면 전역적인 동작 변경이 빨라지고, 코드를 복사하면 지역적인 동작 변경 속도가 빨라진다.
2. 전역적인 동작 변경 속도가 좋다는 것은 코드의 여러 다른 위치에 동시에 영향을 줄 수 있음을 의미한다.
공유 함수의 각 호출자에 서로 다른 지역적인 불변속성이 존재할 수 있다.
공유 코드를 변경할 때마다 이런 지역적인 불변속성이 깨질 위험이 있다. => 취약성이 증가한다.
복제된 코드는 완전히 분리(다른 코드에 영향 X) 되므로 실험과 변경에 유용하다.
따라서 스파이크 동안에는 코드를 복제하여 사용하는 것이 가설을 테스트 하기에 좋은 (빠른) 방법이다.
코드가 안정되면 다음과 같은 질문을 통해 복사한 코드의 통합을 결정한다.
- 원본과 결합해야 하는가?
- 복사본이 바뀌면 원본이 바뀌어야 하는가?
- 팀이 통합된 코드를 가지고 있는가?
위 질문에서 대답이 "아니요"인 경우 별도로 유지한다.
확장성을 통한 추가에 의한 변경
코드를 추가할 때 일부 코드가 변경이 필요하다는 것을 미리 안다면 확장 가능하게 만든다. -> 클래스 생성
모든것을 확장 가능하게 만드는 것은 불필요하다. -> 변형이 발생하는 지점은 코드를 더 복잡하게 만든다
- 우발적 복잡성: 도메인에 관련되지 않은 복잡성
- 본질적 복잡성: 도메인으로부터 발생하는 복잡성
우발적 복잡성 제거하기
1. 코드를 복사한다.
2. 복사본을 가지고 작업해서 적용한다.
3. 합리적이라고 판단될 경우 원본과 통합한다.
위 절차는 데이터베이스에 주요 변경 사항을 안전하게 도입하는 데 사용하는 확장-수축 패턴 (Expand-Contract pattern)과 비슷하다.
1. 확장 단계에서 새로운 기능을 추가 -> 추가만 하기 때문에 유지보수해야할 동일한 동작에 대한 코드도 함께 존재함
2. 호출자를 새로운 기능으로 천천히 이동시키면서 마이그레이션한다. 가장 오래 걸림
3. 모든 호출자의 변경이 완ㄹ되면 수축 단계를 수행해서 원래 버전의 코드를 삭제한다.
"클래스로 타입 코드 대체", "전략 패턴의 도입" 을 활용하여 위 흐름을 구현 할 수 있다.
추가에 의한 변경으로 이전 버전과의 호환성 확보
공개 인터페이스나 API를 통해 기능을 외부에 노출시킬 때가 많다. 사람들이 그런 코드에 의존하면 코드를 갱신할 때 부작용이 생기지 않도록 해야한다. => 버전 관리 를 통해 해결
최대한 안전한 인터페이스를 제공하기 위해서는 이전버전과의 호환성을 유지해야 한다.
이는 변경 할 때마다 수정이 아닌 새로운 인터페이스의 새로운 메서드, 새로은 api end point 를 만드는 것을 의미한다.
방법
1. 변경하려는 기존 엔드포인트를 복제한다.
2.복제한 코드에서 변경사항을 구현
3. 원래 엔드포인트에 통합
이전 버전은 완벽하지 않기 때문에 거기서 비롯된 우발적 복잡성을 추가 시티게 되므로 신규 버전을 사용하도록 튜토리얼을 개신하고 널리 알려서 사람들을 새로운 버전으로 이동시키도록 해야한다.
로깅등의 모니터링을 통해 원본 버전의 사용을 추적한 후 사용하지 않게 되면 제거한다.
메소드가 아닌 사용자와의 인터페이스인 가장 바깥쪽 레이어만 버전화 시킨다.
어떤 버전이 최신 버전인지 쉽게 알 수 있도록 일관된 명명규칙 사용
기능 토글(켜기/끄기)로 추가에 의한 변경
코드 병합을 자주 작은 주기로 하면 오류가 줄어들고 식간이 절약되며 충돌에 대한 두려움도 줄일 수 있다.
준비되지 않은 코드라면?
코드 배포가 곧 릴리즈인 것은 아니다. 코드를 실행하지 않고도 코드 베이스에 포함 시킬 수 있다. (if (false)블록 사용 등)
기능 토글(on/off) 을 사용하는 방법
1. 기존에 없을 경우 FeatureToggle이라는 클래스를 만든다.
2. 추가할 작업에 대해 false를 반환하는 정적 메서드를 추가한다. 이를 기능 플래그 라고 한다.
3. 변경이 구현되어야 하는 위치를 찾는다. 빈 if(FeatureToggle.featureA()) {} 를 넣고 else 에 기존 코드를 넣는다.
4. else의 코드를 if로 복사한다.
5. if 내부의 코드를 원하는대로 변경한다. 새 코드를 테스트할 준비가 되면 FeatureToggle.featureA를 수정해서 환경 변수 값을 반환한다. 변수가 없으면 false 다.
토글기능 단점
- 한 베이스 코드에 로컬 테스트용과 릴리즈용이 공존하기 때문에 의도치 않게 변경사항을 프로덕션 환경에 넣을 수 있다.
- 모든 환경 변수를 활용한 배포 절차를 추가해 기능 전환 없이 올바른 기능으로 롤백할 수 있는 옵션을 제공 할 수 있다.
- 동일한 코드 복사본이 실행되고 있기 때문에 실질적인 복잡성이 증가한다.
- 종속적인 기능을 사용하기 시작하면 두 브랜치에 모두 넣어야 하는 불편함이 있다.
- 기능 토글을 생성한 작업을 마칠 때마다 제거하는 작업도 예약해야한다.
- 기능 플래그는 코드 베이스를 오염시키고 치명적인 오류를 일으킬 수 있기 때문에 오랫동안 유지해서는 안된다.
토글기능의 활용
- 운영자가 on/off 할 수 있는 UI 를 만들어 활용
- 단계적 롤-아웃 구축 - 처음에는 10%의 사용자만 새로운 기능 동작을 확인 한 후 사용자 수 점차 증가
- A/B테스트
'추상화를 통한 분기'로 추가에 의한 변경
'기능 토글이 if 문에서 else를 사용하지 말것을 위반하는 것이 아닌가??'
- 기능 토글에서는 if 문들이 일시적이다. -> 의도된 것이므로 삭제하기 쉽고 if문 문제들을 확장시키지 않는다.
- 여러개의 if 문을 사용하게 되면 기능 플래그 내부의 Boolean에 클래스로 타입 코드 대체 패턴을 사용한다.
- true, false를 리턴하는 대신 NewA 또는 OldA 와 같은 것을 반환한다. -> "추상화를 통한 분기"
'스터디 > Five Lines of Code' 카테고리의 다른 글
Five Lines of Code 12장. 최적화 및 일반화 회피 (0) | 2023.11.17 |
---|---|
Five Lines of Code 11장. 코드 구조 따르기 (0) | 2023.11.16 |
Five Lines of Code 9장. 코드 삭제의 미학 (0) | 2023.11.05 |
7장 컴파일러와의 협업 (1) | 2023.11.04 |
Five Lines of Code 6장. 데이터 보호 (1) | 2023.10.29 |