디시인사이드 갤러리

마이너 갤러리 이슈박스, 최근방문 갤러리

갤러리 본문 영역

생산성을 높이는 점진적 프로그래밍

Dunkirka갤로그로 이동합니다. 2021.05.26 10:18:07
조회 446 추천 0 댓글 0
														

https://www.gamasutra.com/blogs/NiklasGray/20210524/381868/Stepbystep_Programming_Incrementally.php




단계별: 점진적으로 프로그래밍

나의 생산성(그리고 나의 전반적인 정신)에 정말로 도움이 된 한 가지는 큰 일을 어떻게 해서 더 작고 다루기 쉬운 단계로 분해하는지를 배워왔다. 큰 일은 무섭고 압도적일 수 있지만, 작은 일들의 목록만 계속 작업한다면, 어떻게 해서든지, 마치 마술처럼 큰 일이 완성된다.

부가가치

프로그래밍할 때, 나는 이 분류에 대해 매우 구체적인 접근방식을 취한다. 나는 각 단계가 컴파일하고, 실행하며, 모든 테스트통과하고, 코드베이스에 가치를 더하는 것임을 확실히 한다.정확히 'addds value'가 의미하는 바는 의도적으로 모호하게 남겨두지만, 작은 특징 추가, 버그 수정, 또는 코드를 더 나은 형태로 리팩터링(즉, 기술적 부채의 감소)하는 한 걸음 더 나아가는 것과 같은 것들이 될 수 있다.

가치를 추가하지 않는 예로는 새로운 기능을 추가하면서도 10개의 새로운 버그를 도입하는 것이 있다. 특징의 가치가 버그 비용보다 크다는 것이 분명하지 않기 때문에 순손실일 수도 있다.

가치를 추가하지 않는 또 다른 예는 UI를 더 예쁘게 만드는 동시에 앱 실행 속도를 10배 더 늦추는 것이다. 다시 말하지만, 더 예쁜 외모가 공연히 히트할 만한 가치가 있다는 것이 분명하지 않기 때문에 순손실일 수도 있다.

물론 내가 버그를 도입하지 않는다고 항상 확신할 수는 없고, '가치'는 본질적으로 주관적인 것이다(얼마나 성과가 새로운 특징 가치인가). 중요한 부분은 의도다.의도는 항상 모든 커밋에 가치를 더하는 것이다.

기본적으로, 내가 피하고 싶은 것은 "더 나빠져야 더 나아진다"-관심이다. 또 다른 명칭: "새로운 시스템은 그것을 하는 현대적인 방법이다. 물론 지금은 벌레가 좀 있고 좀 느리게 달리긴 하지만 일단 고치고 나면 이전보다 훨씬 나아질 겁니다." 나는 이런 해결책들이 결코 일어나지 않는 경우를 너무 많이 봐왔고, 더 나아져야 할 새로운 시스템이 상황을 더 악화시켰다.

게다가 가치를 더하면 기분이 좋아지잖아. 만약 내가 매일 커밋을 할 수 있고 그 커밋이 어떤 면에서 엔진을 더 좋게 만든다면, 그것은 나를 행복하게 만든다.

마스터에 푸시하기

일련의 작은 약속들을 통해 변화를 구현하는 것 외에도, 나는 또한작은 약속들 하나하나를 마스터 지부에 다시 밀어 넣는다.

이는 피쳐 분기 워크플로와는 정반대인 대신 트렁크 기반 개발의 한 형태라는 점에 유의하십시오.

  • 기능 분기 워크플로우 개발자는 격리된 개별 코드 분기에서 새로운 기능에 대해 작업하며 완전히 작동, 디버깅, 문서화, 코드 검토 등 "완전"할 때까지 마스터에 다시 병합하지 마십시오.

  • 트렁크 기반 접근법에서 특징은 일련의 작은 개인이 마스터 분기 자체에 위임함에 따라 구현된다. 기능이 "부분적으로만 구현"된 경우에도 모든 기능이 작동하도록 주의를 기울여야 한다.

Trunk-based vs feature branch development.

트렁크 기반 및 기능 분기 개발.

피쳐 분기 접근방식의 지지자들은 피쳐 분기의 변경이 마스터를 방해하지 않고 버그를 야기하지 않기 때문에 그것이 더 안전한 작업 방법이라고 주장한다. 개인적으로, 나는 이 안전이 환상적이라고 생각한다. 피쳐 분기의 버그는 우리가 갑자기 모든 버그를 얻었을 때 마스터로 다시 합쳐질 때까지 숨겨진다.

특색 부문은 또한 모든 커밋이 가치를 추가해야 한다는 나의 철학과 어긋난다. 특집 기사 뒤에 숨겨진 생각은 "여기서 똥덩어리들을 부숴버릴 거야. 하지만 걱정하지 마. 우리가 다시 마스터로 합류하기 전에 고칠 거야."이다. 애초에 물건을 망가뜨리지 않는 것이 좋다.

트렁크 기반 접근방식에서 볼 수 있는 몇 가지 다른 이점이 있다.

  • 병합 충돌 감소. 피쳐 분기가 오래 걸리면 분기의 코드가 마스터의 코드에서 점점 더 멀어지게 되어 점점 더 많은 병합 충돌을 일으킨다. 이것들을 다루는 것은 프로그래머들에게 많은 바쁜 일이고 또한 버그를 소개할 위험도 있다. 이 벌레들 중 일부는 지부가 합병될 때까지 보이지 않을 것이다.

  • 개봉일 대혼란이 줄어들었다. 일반적으로 특정 릴리스로 예약된 모든 기능은 동일한 마감일을 가진다. 이는 마감 직전에 모든 피쳐 지점이 통폐합되는 결과로 이어진다. 출시 직전에 모든 병합 버그와 통합 버그를 동시에 얻는 셈이다. 동시에 많은 벌레를 얻는 것은 균일하게 벌레를 펴는 것보다 훨씬 더 나쁘다. 그리고 개봉일이 되기 직전에 그것들을 얻는 것은 그들을 얻기 위한 절대 최악의 시간이다.

  • 합병을 위한 적절한 시기에 대해 걱정하지 마십시오. 피쳐 분기를 합병하면 불안정이 초래된다는 것을 모두가 알고 있기 때문에, 이것은 합병할 "적기"에 대한 걱정으로 이어진다. 릴리스에 버그가 도입되지 않도록 릴리스 바로 전에 병합하지 마십시오(릴리스에 기능이 필요하지 않은 경우). 그럼 개봉 직후에? 하지만 석방을 위해 핫픽스가 필요하다면? 합병이 진행되는 동안 귀중한 프로그래머 시간은 낭비되고 있다.

  • 합병을 서두를 필요 없다. 피처 지사와 함께 일할 때 지사를 통폐합하는 데 서둘렀다는 느낌을 자주 받았다. 때로는 특정 출시를 위해 지점이 필요했기 때문이다. 그러나 또한 종종 개발자가 합병 분쟁을 처리하는 데 지쳤고, 그것을 끝내기를 원했고, 다음으로 넘어가기를 원했기 때문이다. 따라서 형상 분기가 "완전"할 때만 합치겠다는 목표는 종종 훼손되었다. (물론, 실제로 "완전"한 것은 없다.)

  • 되돌리기가 쉽다. 피쳐 분기(흔히 일어나는 일)의 합병 후 주요 이슈가 발견되면, 합병의 되돌리기를 꺼리는 경우가 많다. 또 다른 큰 형상 분기는 이미 그 위에 병합되었을 수 있으며(많은 형상 분기가 출시 직전에 동시에 병합되는 경우가 많기 때문에), 되돌리면 총합병 대란이 발생할 수 있다. 그래서 팀은 차분하고 분별 있는 롤백을 하는 대신에 개봉 전에 문제를 해결하기 위해 필사적으로 싸워야 한다. 트렁크 기반 개발로, 어떤 중요한 문제라도 이미 발견되었을 가능성이 크다. 새로운 기능을 "실행"하게 만드는 최종 커밋은 일반적으로 되돌리기에는 고통이 없는 단순한 한 줄의 변화다.

  • 부분적인 업무는 공유한다. 트렁크 기반 개발에서 기능에 대해 수행된 부분 작업은 (마스터 분기의) 모든 개발자가 볼 수 있다. 그러므로, 모든 사람들은 엔진이 어디로 가는지 잘 알고 있다. 버그, 디자인 결함, 그리고 다른 문제들은 일찍 발견될 수 있다. 그리고 다른 사람들이 그들의 코드를 새로운 특징과 함께 작동하도록 적응시키는 것이 더 쉽다. 또한 모든 사람이 기능이 얼마나 진행되었는지 볼 수 있을 때 기능을 완성하기 위해 얼마나 많은 작업이 필요한지에 대한 견적을 얻는 것이 더 쉽다.

  • 잠시 멈추고 나중에 받기가 더 쉽다. 때때로 기능 작업은 다양한 이유로 일시 중지해야 할 수 있다. 해결해야 할 더 중요한 문제들이 있을 수 있다. 아니면 이 기능의 주요 개발자가 병에 걸리거나 곧 휴가를 갈 수도 있다. 이는 피쳐 분기마다 코드베이스가 점점 더 멀어져 지점과 점점 더 병합 충돌을 일으키기 때문에 시간이 지날수록 '회전'하는 경향이 있기 때문에 문제가 된다. 마스터로 체크하는 코드는 같은 방법으로 "회전"하지 않는다.

  • 다른 버그/리프랙터를 동시에 다루기가 더 쉽다. 특징이나 문제를 다룰 때, 당신이 하고 있는 일에 의해 노출되는 다른 관련 문제들을 발견하는 것은 꽤 흔한 일이다. 트렁크 기반 접근법에서 이것은 문제가 아니다. 당신은 단지 그 문제들을 해결하기 위해 트렁크에 하나 또는 그 이상의 분리된 약속을 할 것이다. 피쳐 분기 접근 방식으로는 더욱 까다롭다. 내 생각엔 새로운 버그 수정 분기를 마스터에서 분리하고, 그 분기의 문제를 수정하고, 그 분기를 현재 작업 중인 분기로 병합하고, (코드 검토가 통과되면) 마스터로 통합하는 것이 옳은 일일 것 같은데, 왜냐하면 그것이 언제 발생할지 누가 알 수 있기 때문이다.하지만 누가 그런 똥을 쌌을까? 그래서 대신, 사람들은 단지 그들의 특집 분기에 문제를 고치고, 좋은 하루를 보내고 있다면 그것을 마스터에게 고를 수도 있다. 그래서 이제는 하나의 고립된 형상에 관한 것이 아니라 형상 분기는 서로 다른 형상과 버그 수정, 리팩터의 뒤엉킨 혼합물이 된다.

트렁크 기반 접근법의 주요 과제는 어떻게 하면 큰 일을 개별적인 것으로 분해할 수 있는가 하는 것이다. 특히, 각 작품을 컴파일하고, 실행하며, 가치를 더하고, 마스터에 밀어 넣을 준비를 해야 한다는 요구조건으로 한다. 사용자들을 설익은, 아직 완전히 작동되지 않은 기능들에 노출시키지 않고 어떻게 부분적인 작업을 추진할 수 있을까?

몇 가지 문제점과 해결 방법을 살펴보자.

문제 #1: 새 기능

새로운 기능에 잘 적용되는 접근방식은 플래그를 사용하여 최종 사용자가 해당 기능을 볼 수 있는지 여부를 제어하는 것이다.

예를 보자. 최근에 엔진에 추가한 기능은 사용자가 엔진 자체 내에서 새로운 엔진 버전과 샘플 프로젝트를 다운로드할 수 있는 다운로드 탭이었습니다.

Download tab.

다운로드 탭.

이것을 작은 단계로 나눌 수 있는 많은 다른 방법들이 있다. 예를 들면 다음과 같다.

  • 메뉴에 다운로드 탭을 추가하고 열면 새 빈 탭을 표시하십시오.
  • (하드코딩된) 다운로드 버튼이 작동하지 않는 다운로드 항목 목록 표시
  • 하드 코딩되지 않고 서버에서 파일 목록을 다운로드하십시오.
  • 구현: [Get] 클릭했을 때 파일을 동기적으로 다운로드하여 버튼을 누른다.
  • 비동기식 백그라운드 다운로드로 전환하십시오.
  • 다운로드 진행률 표시줄 표시

보통은 이렇게 맨 앞에서 풀헤레이션을 하지 않는다. 대신에, 나는 내가 진행하면서 다음 논리적인 단계를 알아낼 뿐이다. 일이 특히 까다롭고 이런 증분적 단계가 자연스럽게 오지 않으면 나는 앉아서 진지한 계획수립을 할 뿐이다.

최종 사용자가 실제로 작동하기 전에 탭을 보지 못하도록 플래그 뒤에 숨긴다. 이는 다음과 같이 간단할 수 있다.

const bool download_tab_enabled = false;  // ...  if (download_tab_enabled) {     add_menu_item(tabs, "Download", open_download_tab); } 

다운로드 탭을 표시하는 메뉴 옵션을 보려면 download_tab_enabled 로 플래그를 달다. true 그리고 다시 컴파일한다.

응용 프로그램의 개별 기능을 선택적으로 활성화하거나 비활성화하기 때문에 이러한 플래그를 피쳐 플래그라고 부른다. 기능이 완료되면 깃발을 떼고 그냥 자리를 뜨면 된다. true 암호 경로

기능 플래그를 구현하는 세 가지 주요 방법이 있다.

  • a를 통해 #define 체크한 매크로 #ifdef.

  • a를 통해 const bool (위의 예와 같이) 암호로.

  • 다이나믹을 통해 bool 구성 파일, 메뉴 옵션 또는 내부 디버그 콘솔에서 초기화된 변수.

이 중에서 세 번째 것이 가장 좋다고 생각한다. 코드를 가능한 많은 사람에게 노출하려는 경우. 그렇게 하면, 그들은 당신의 코드에서 버그를 찾을 수 있고, 만약 그들이 코드베이스를 리팩터링한다면, 그들은 당신의 코드를 고려하는 등등을 할 수 있다.

a를 사용하면 #define 플래그, 당신 팀의 다른 사람들은 당신의 코드를 컴파일하지도 않을 겁니다. 따라서, 그들의 변화 중 하나는 쉽게 당신의 코드를 깨뜨릴 수 있다. a const 당신의 코드가 여전히 컴파일될 것이기 때문에 플래그가 더 낫지만, 사람들은 당신의 코드를 다시 컴파일하지 않고는 새로운 기능을 시도할 수 없기 때문에, 대부분의 사람들은 신경 쓰지 않을 것이다.

동적 플래그를 사용하면 애플리케이션 재구축에 방해가 되지 않는 아티스트, 프로듀서 또는 최종 사용자가 구성 파일을 수정하고 새로운 기능을 테스트 실행하면 된다.

피쳐를 해제할 때가 되면 피쳐 플래그의 기본값을 다음에서 플립하십시오. falsetrue 그리고 모든 사람들이 새로운 특징을 보게 될 것이다. 만약 문제가 있고 되돌아가야 한다면, 그냥 깃발을 뒤로 젖혀라. 나중에 특징이 안정되어 보이면 깃발을 없애고 코드에 참된 길만 지키면 된다.

단계별 부분 롤아웃도 할 수 있다. 예를 들어 1%의 사용자에 대해 플래그를 true로 설정한 다음 충돌 로그와 포럼에서 문제가 발생하는지 모니터링하면서 플래그를 천천히 증가시킬 수 있다. 그래야 문제가 발생하면 소수의 사용자에게만 영향을 미치고 빠르게 되돌릴 수 있다.

문제 #2: 다시 쓰기

위의 접근방식은 새로운 특징에 대해 잘 작동하지만, 기존 시스템을 대대적으로 다시 쓰는 경우 어떻게 해야 하는가?

이 경우 기존 시스템의 많은 부분을 제거하고 교체 코드가 피쳐 패리티에 도달할 때까지 시간이 걸릴 수 있으므로 단계별 접근 방식을 찾는 것이 더 까다로울 수 있다.

이 경우 피쳐 분기 접근법에 도달하는 것이 유혹적일 수 있지만, 다시 말하지만, 나는 이것이 올바른 전략이 아니라고 생각한다. 큰 문제는 두 가지 상반되는 목표를 갖게 된다는 것이다. 한편, 당신은 사람들이 개선된 코드를 볼 수 있도록 일찍 합병하고 당신은 재작성에 대한 테스트를 받기를 원한다. 한편, 가능한 한 오래 지연시켜 피쳐 패리티에 도달하고 모든 버그를 다림질할 수 있도록 하고 싶은 것이다. 보통 일어나는 일은 그 둘의 만족스럽지 못한 혼합이다.

더 나은 접근방식은 병렬로 구현하고 엔진에 동일한 시스템의 복사본 2개를 갖는 것이다.

만약 당신이 대대적인 점검을 하고 있다면, 당신은 단지 전체 시스템 코드를 새 폴더에 복사하는 것부터 시작할 수 있다. 처음부터 다시 쓰는 경우에는 빈 폴더로 시작할 수 있다.

교체하는 시스템의 특성에 따라 두 시스템(이전 시스템과 새 시스템)을 병렬로 실행하거나, 기본 시스템을 선택하는 기능 플래그가 있을 수 있다. 예를 들어, 물리학 시뮬레이션을 다시 쓰는 경우, 모든 물리학 개체가 동일한 시뮬레이션에서 생활하기를 원하기 때문에 이전 것을 사용하는지 또는 새 것을 사용하는지를 선택하는 플래그가 필요할 수 있다(그렇지 않으면 상호작용하지 않는다). 반면에, 만약 당신이 입자 효과 시스템을 다시 쓰고 있다면, 당신은 잠재적으로 두 시스템을 실행하고 단지 그것이 이전 시스템에서 재생되어야 하는지 새로운 시스템에서 재생되어야 하는지를 각각의 재생 효과에 대해 선택할 수 있다.

병렬 구현을 통해 이전 시스템에서 새 시스템으로 훨씬 더 원활하게 전환 가능 팀의 모든 사람들이 새로운 시스템을 쉽게 시험해 볼 수 있다. 특징 완성도, 안정성, 성능 등을 위해 예전 것과 비교해 볼 수 있다. 해당되는 경우 자동 테스트를 실행하여 새 시스템이 이전 시스템과 동일한 출력을 생성하는지 확인할 수도 있다. 새 시스템이 철저히 검증되면 플래그를 변경하고 기본값으로 사용할 수 있다.

또한 병렬 구현은 최종 사용자에게 훨씬 더 부드러운 업그레이드 경로를 제공한다. 모든 사용자를 동일한 "merge date"로 묶는 대신 최종 사용자에게 시스템 선택 플래그를 노출하면 된다. 새로운 시스템에서 개선된 기능을 시험해 보고자 하는 사용자는 기를 일찍 켤 수 있고, 새 시스템이 디폴트가 된 후에도 기존 시스템에서 안정성을 선호하거나 특정 기질에 의존하는 사용자는 깃발을 계속 달기로 결정할 수 있다.

그리고 일단 새로운 제도가 디폴트가 되면, 당신은 결국 그것을 유지하는 것에 대한 부담이 그것을 가지고 있는 것의 가치보다 더 크면, 구 제도를 폐지하고 단계적으로 폐지할 수 있다.

문제 #3: 리팩토링

우리의 마지막 문제를 위해, 더 까다로운 것을 고려해보자. 즉 전체 코드베이스에 큰 리팩터링 변경을 하는 것이다. 다음과 같은 것일 수 있다.

  • 다음과 같은 새 경고 사용 -Wshadow 또는 -Wunused.
  • 일반적으로 사용되는 함수의 매개 변수 변경.
  • 다음에서 전환과 같이 일반적으로 사용되는 유형 변경 std::string 내부 문자열 유형으로.

이와 같은 리팩터의 과제는 다음과 같다.

  • 그들은 코드를 많이 만지는 경향이 있어서 합병 충돌의 위험을 증가시킨다.

  • 그것들을 어떻게 점진적으로 하는지를 보는 것은 종종 까다롭다. 예를 들어, 함수의 매개변수를 변경하는 경우, 모든 통화 사이트를 업데이트해야 한다. 그렇지 않으면, 그 코드는 단순히 컴파일되지 않을 것이다.

어떤 경우에는 문제를 점진적으로 접근하는 방법이 매우 간단할 수 있다. 예를 들어 다음과 같은 새 경고를 추가할 때 -Wshadow, 경고와 함께 코드를 컴파일하기 위해 우리가 하는 수정은 경고 없이 코드를 컴파일할 때 문제를 일으키지 않는다. 따라서 우리는 단순히 경고를 켜고, 적절한 크기의 오류를 수정한 다음, 경고를 다시 끄고, 결과를 발생시킬 수 있다. 모든 경고가 고정될 때까지 헹구고 반복한 다음 최종 커밋을 수행하여 -Wshadow 모두를 위해 깃발을 꽂다

다른 경우에는 병행 시행 전략을 채택할 수 있다. 코드 전체에 걸쳐 메모리를 할당하는 기능이 있다고 가정합시다.

void *mem_alloc(uint64_t bytes); 

이제 우리는 a를 추가하기를 원한다. system 모든 메모리 할당이 특정 시스템(게임 플레이, 그래픽, 사운드, 애니메이션 등)에 속하는 것으로 태그 지정될 수 있도록 매개 변수:

void *mem_alloc(uint64_t bytes, enum system sys); 

이렇게 파라미터를 추가하기만 하면 모든 콜 사이트를 고치기 전에는 코드가 컴파일되지 않을 겁니다. 즉, 증분적 접근법은 작동하지 않을 것이다.

대신 우리가 할 수 있는 것은 새로운 병렬 기능을 도입하는 것이다.

void *mem_alloc(uint64_t bytes); void *mem_alloc_new(uint64_t bytes, enum system sys); 

이제 모든 코드를 점진적으로 전환하여 mem_alloc_new() 대신에 mem_alloc()코드를 모두 바꾸면 제거할 수 있다. mem_alloc() 이름을 바꾸는 글로벌 검색 및 검색 mem_alloc_new()mem_alloc().

마지막 도전은 코드베이스의 근본적인 유형 중 하나를 바꾸는 가장 까다로운 도전이다.

사실, 나는 최근에 이것과 우연히 마주쳤는데, 그것이 내가 이 블로그 게시물을 전부 쓰게 한 계기가 되었다. 나의 경우, 나는 The Truth에서 사물의 ID를 나타내는 새로운 유형을 소개하기 위해 우리의 코드베이스를 리팩터링하고 싶었다. The Truth는 기계에 있는 우리의 주요 데이터 저장소다. 편집자가 작업하는 모든 데이터는 진실(The Truth)에 저장되며, 진실(The Truth)의 모든 개체는 그 ID로 참조된다.

우리는 진실 ID를 단지... uint64_t, 그러나 우리는 또한 표현되는 많은 다른 가치들을 가지고 있기 때문에. uint64_t이것은 점점 더 많은 혼란을 야기시키고 있었다. 진실 ID가 예상되는 위치 및 통과에 대한 일부 유형 안전성에 대한 더 나은 문서화 제공 uint64_t 가치관, 우리는 평원을 사용하는 것에서 바꾸기로 결정했다. uint64_t 구조물로 포장하려면:

typedef struct tm_tt_id_t {     uint64_t u64; } tm_id_tt_t; 

True ID는 어디에서나 사용되므로 이러한 변경은 코드의 수천 줄에 영향을 미친다는 점에 유의하십시오.

다소 오만하게, 나는 여전히 이 리팩터를 집중적인 한 번의 밀기로 시작했어. 나도 내가 점진적으로 일을 해야 한다는 걸 알아, 그게 이 블로그 게시물 전부야. 하지만 가끔 내가 망칠 때도 있어.

그 일을 하면서 나는 이 변화가 원래 생각했던 것보다 훨씬 크고 한 자리에서 끝낼 수 있는 방법이 없다는 것을 깨달았다. 나는 일이 걷잡을 수 없이 소용돌이치고 있다는 그 가라앉는 느낌을 갖기 시작했다. 는 수천 줄의 코드를 바꾸고 있었다. 내가 오타를 아무데서나 소개하는게 아니라고 정말 확신했던가?

많은 변화들이 다음과 같은 것들을 변화시키고 있었다.

if (!object_a) { 

다음 항목으로:

if (!object_a.u64) { 

내가 가끔 타이핑을 하지 않는다고 정말 100% 확신했는가?

if (object_a.u64) { 

대신에? 몇 가지 테스트를 실행하면 자신감이 높아졌을 텐데, 모든 을 고쳐야 코드가 컴파일되는 것이기 때문에, 나는 어떤 테스트도 실행할 수 없었다.

그리고 나서 나는 아무것도 깨뜨리지 않을 만큼 그들의 코드를 잘 이해하기를 바라면서 내가 손으로 해결해야 했던 다른 사람들의 변화들과 병합 충돌을 일으키기 시작했다. 모든 변화의 결과를 상상하는 것은 점점 더 어려워졌다. 내가 소개한 벌레는 몇 마리인가?

과거에 나는 종종 이러한 "패닉" 감정을 "힘으로써" 다루려고 노력해왔다. 내가 통제할 수 있게 다시 내 몸에서 빠져나가는 걸 뺏기 위해 긴 심야 코딩 시간을 끌어내기 시작했어 다른 사람이 합병 분쟁을 처리하지 않도록 내 모든 변경사항을 다른 사람이 밀어붙이기 전에 먼저 처리해. (물론 그 말은 단지 그들이 대신 합병 갈등을 다루어야 한다는 것을 의미한다. 하하!)

하지만 요즘 내가 좀 더 현명해졌을까? 나는 이러한 감정들이 내가 씹을 수 있는 것보다 더 많이 물어뜯었다는 징조라는 것을 깨닫고 올바른 대응은 앞으로 나아가는 것이 아니라 한 걸음 뒤로 물러서서 반성과 개혁을 시도하여 그 대신 일련의 작은 단계로 다가갈 수 있도록 한다. 내가 얼마나 많은 곤경에 빠졌느냐에 따라, 나는 약간의 변화를 살릴 수도 있고, 아니면 그냥 모든 것을 버리고 반복적인 접근으로 다시 시작할 수도 있다. 모든 것을 버려야 할 때도 한 번도 후회한 적이 없다. 결국, 작고 통제된 일련의 단계에서 일하는 것은 훨씬 더 생산적이어서, 내가 "잃어버린" 시간을 늘 되찾은 것 같은 기분이 든다.

당면한 문제로 돌아가라. 나는 내가 점진적으로 유형을 바꾸고 싶어한다는 것을 알았다. 즉, 일부 파일에서는 변경하지만 다른 파일에서는 변경하지 않고 여전히 코드를 컴파일하고 테스트할 수 있다. 하지만 모든 것이 다른 모든 것에 달려 있는데 어떻게 그럴 수가 있지? 사용할 일부 매개 변수를 변경하면 tm_tt_id_t, 그것을 부르는 모든 것. uint64_t 오류를 발생시킬 것이다.

나의 주요 통찰력은 이 상황이 가능성의 상황과 매우 유사하다는 것이었다. -Wshadow그 전환은 우리가 단지 아래 컴파일에서 코드를 변경했기 때문에 하기 쉬웠다. -Wno-shadow 양쪽 다 합치도록. -Wshadow 그리고 -Wno-shadow이 변경은 마지막으로 켜질 준비가 될 때까지 점진적으로 수행할 수 있다. -Wshadow.

여기서도 똑같이 하자. ID 객체의 유형을 호출하여 ID_TYPE지금, 우리의 코드는 ID_TYPE 이다 uint64_t우리의 목표는 그 코드를 다시 쓰는 것이다. 그래서 그것은 두개의 코드를 컴파일할 수 있도록. ID_TYPE 이다 uint64_t 그리고 그것이 있을 때 tm_tt_id_t. 만약 우리가 점진적으로 그 상태로 전환할 수 있다면, 일단 모든 코드는 ID_TYPE 이다 tm_tt_id_t 디폴트로 만들 수 있어

이 기능이 어떻게 작동하는지 보여 주는 간단한 예:

uint64_t get_data(uint64_t asset) {     const tm_the_truth_object_o *asset_r = tm_tt_read(tt, asset);     uint64_t data = api->get_subobject(tt, asset_r, TM_TT_PROP__ASSET__OBJECT);     return data; } 

이 값을 의 값과 독립적으로 만들기 위해 ID_TYPE, 우리는 그것을 다음과 같이 다시 쓸 수 있다.

ID_TYPE get_data(ID_TYPE asset) {     const tm_the_truth_object_o *asset_r = tm_tt_read(tt, asset);     ID_TYPE data = api->get_subobject(tt, asset_r, TM_TT_PROP__ASSET__OBJECT);     return data; } 

이것은 유무에 관계없이 효과가 있다. ID_TYPE 이다 uint64_t 또는 tm_tt_id_t헤더 파일에는 다음과 같은 내용을 담고 있다.

typedef struct tm_tt_id_t {     uint64_t u64; } tm_tt_id_t;  #define TM_NEW_ID_TYPE 0  #if TM_NEW_ID_TYPE     typedef tm_tt_id_t ID_TYPE; #else     typedef uint64_t ID_TYPE; #endif 

우리는 이제 우리의 약속을 지킬 수 있다. get_data() 코드에서 다른 어떤 것도 변경할 필요 없이 기능한다. 로 인해 모든 것이 여전히 컴파일될 것이다. ID_TYPE 기본적으로 다음과 같이 정의됨 uint64_t그래서 전화하는 모든 장소들 get_data() …과 함께 uint64_t 여전히 효과가 있을 거야 우리는 ID 타입을 바꾸는 작은 점진적인 조치를 취했다.

우리가 그랬던 것처럼 한 발짝 더 나아가기 위해 -Wshadow, 우리가 정했다. TM_NEW_ID_TYPE=1 지방적으로 이렇게 되면 수천 개의 오류가 발생하겠지만 한꺼번에 고칠 필요는 없다. 우리는 단지 우리가 했던 것처럼 코드를 수정함으로써 (적절한 커밋 청크가 무엇이든 간에) 몇 개만 고친다. get_data()그리고 나서 우리는 변한다. TM_NEW_ID_TYPE 0으로 돌아가서 모든 것이 여전히 컴파일되고 정상적으로 작동하는지 확인하십시오. 만약 그렇게 된다면, 우리는 그 변화들을 저지를 수 있다. 전체 코드베이스가 고정될 때까지 헹구고 반복하십시오.

그냥 변하는 상황이 있을 수도 있다. uint64_tID_TYPE 효과가 없다. 예를 들어 코드베이스에는 다음과 같은 몇 가지 코드가 있다.

bool objects_equal(uint64_t a, uint64_t b) {     return a == b; } 

우리는 엄격한 C 코드를 쓰고 있기 때문에, 우리는 실행할 수 없다. operator== 을 위해 tm_tt_id_t다음 항목과 비교해 보십시오.

return a.u64 == b.u64; 

(측면 참고: 나는 종종 C 구조에서 효과가 있기를 바란다. 컴파일러는 구조체에 패딩 바이트가 포함되어 있는지 여부를 결정해야 한다. 그렇지 않다면 == 는 다음과 같이 구현되어야 한다. memcmp, 그렇지 않으면, 회원별 비교로서)

다시 한번, 우리의 목표는 이전 코드를 새로운 코드로 대체하는 것이다. 새로운 코드는 가능한 두 가지 값 모두를 컴파일한다. ID_TYPE. 이것은 우리가 그냥 쓸 수 없다는 것을 의미한다. a.u64, 왜냐하면 그것은 다음과 같은 경우에 컴파일되지 않을 것이기 때문이다. a 이다 uint64_t하지만 우리는 전처리기사를 다시 한 번 활용하고 다음과 같은 것을 쓸 수 있다.

#if TM_NEW_ID_TYPE     #define TO_U64(x) ((x).u64) #else     #define TO_U64(x) (x) #endif 

여기, 더 TO_U64 매크로가 하나의 결과를 낳는다. uint64_t 으로부터 ID_TYPE …을 불문하고 반대하다 ID_TYPE 포장되어 있는지 아닌지. 이제, 우리는 다시 쓸 수 있다. objects_equal() 다음과 같이 기능한다.

bool objects_equal(ID_TYPE a, ID_TYPE b) {     return TO_U64(a) == TO_U64(b); } 

그리고 그 코드는 새로운 ID 타입과 이전 ID 타입 둘 다로 컴파일 될 것이다.

이것을 하는 다른 방법들도 있다. 예를 들어, 대신 TO_U64(x), 우리는 창조할 수 있었다. ID_EQUAL(a,b) 매크로의

요점은 전처리기기를 사용함으로써, 우리가 가지고 있는 어떤 코드든 가져갈 수 있어야 하고, 그것이 가능한 두 가지 값을 모두 가지고 컴파일할 수 있도록 다시 쓸 수 있어야 한다는 것이다. TM_NEW_ID_TYPE. 일단 그렇게 할 수 있게 되면, 우리는 점점 더 많은 코드를 새로운 타입으로 바꿀 수 있다.

일단 전체 코드가 변환되면 우리는 TM_NEW_ID_TYPE=0 코드 경로 그리고 우리가 그렇게 원한다면, 모든 과도기 매크로를 제거한다. ID_TYPE, TO_U64, 등으로 교체하여 TM_NEW_ID_TYPE=1 가치

이 접근법을 사용하여, 나는 완전히 에서 전환할 수 있었다. uint64_ttm_tt_id_t 10-15개의 증분 커밋이 이틀에 걸쳐 수행되며, 항상 컴파일 및 테스트를 통해 변경으로 인해 어떤 것도 깨지지 않았는지 확인할 수 있다. 다른 팀원들로부터의 커밋은 많은 합병 문제를 일으키지 않았다. 왜냐하면 나의 커밋은 모두 짧은 시간(한 시간 정도 일)이었고, 그 많은 파일들을 건드리지 않았기 때문이다. 이 크고 무서운 변화는 갑자기 매우 감당할 수 있게 되었다.

결론들

증분 패턴은 어디에나 적용될 수 있을 것 같아. 점진적인 방식으로 문제를 수정하기 위해서는 약간의 창의력이 필요할 뿐이고 이것은 연습을 통해 더 잘하게 되는 것이다.

일을 점진적으로 하는 것은 간접비가 조금 더 많이 든다. 예를 들어, 우리는 바꿀 수 없었다. objectobject.u64 한 번에, 우리는 에 의해 전환되어야만 했다. TO_U64(object)하지만, 나는 여분의 시간이 곧 다시 생긴다고 생각한다. 점진적으로 일함으로써 목표를 향해 차분하고, 꾸준하며, 자신감 있는 진전을 이룰 수 있고, 결국 훨씬 더 빨리 일을 할 수 있게 된다. 예를 들어, ID 타입의 변경으로, 나는 정규 표현식으로 글로벌 검색과 교체를 사용하여 많은 변경을 할 수 있었다. 만약 내가 규칙적으로 코드를 컴파일하고 테스트할 수 없었다면, 검색과 교체가 옳은 일을 하고 있다고 믿는 것은 훨씬 더 힘들었을 것이다.

또한, C 전처리는 언어에 있어서 좀 이상한 것이지만, 가끔은 이런 것을 해야 할 때, 그것이 그곳에 있다는 것이 정말 멋질 때도 있다.


추천 비추천

0

고정닉 0

댓글 영역

전체 댓글 0
등록순정렬 기준선택
본문 보기

하단 갤러리 리스트 영역

왼쪽 컨텐츠 영역

갤러리 리스트 영역

갤러리 리스트
번호 제목 글쓴이 작성일 조회 추천
설문 주위 눈치 안 보고(어쩌면 눈치 없이) MZ식 '직설 화법' 날릴 것 같은 스타는? 운영자 24/04/29 - -
AD [원신] 신규 5성 아를레키노 등장 운영자 24/04/26 - -
공지 게임 기획 관련 사이트 모음 (2021.5) [7] Dunkirka갤로그로 이동합니다. 18.10.30 1518 1
공지 게임기획 잘하는 방법 Dunkirka갤로그로 이동합니다. 19.08.29 968 3
공지 게임 기획서(GDD) 총정리 (2018.11) [3] Dunkirka갤로그로 이동합니다. 18.10.30 3028 3
공지 게임 디자인 관련 추천 도서 (2018.11) [6] Dunkirka갤로그로 이동합니다. 18.11.12 5719 1
555 게임하면서 컴터 켜놓기만해도 쌀먹 쌉가능?? 필독 ㅇㅇ(146.70) 04.12 21 0
552 제19회 경기게임 오디션 안내 스펙토리갤로그로 이동합니다. 03.26 24 0
551 아우디 로고 디자인 수정 건의. TXT 미노루갤로그로 이동합니다. 03.13 33 0
550 아우디 로고 일케 바꾸면 훨 좋을 듯. TXT 텐구아레스갤로그로 이동합니다. 02.25 32 0
548 김실장 채널 꽤 괜찮네 게갤러(112.156) 23.11.29 150 1
547 게임 기획/개발/디자인 경험을 살려 창업을 할 수 있다면? 함께가자(14.4) 23.11.17 105 0
545 게임 개발 (진지한 글 주의) 부트캠프 후기 아이폰어플개발(125.131) 23.09.25 193 0
544 [취업을 도와줄 PM 템플릿/자료] 개발자갤로그로 이동합니다. 23.09.22 111 0
542 게임 만들기 ㅇㅇ(203.228) 23.08.03 100 0
541 비대면진료,맞춤주치의 닥터루시드를 만들고자 합니다_크로스앱개발자2(RN) 맞춤주치의(39.123) 23.08.01 54 0
540 [정보] 게임 개발에 관심이 있으신 경우 ㅇㅇ(210.103) 23.06.30 358 1
534 게임 기획 갤을 찾아온 뉴비들아 현업 질문 간절하면 연락해라 겜갤러(221.150) 23.02.20 444 2
533 갤에 유용한글 만네 ㅇㅇ(210.95) 22.12.26 217 0
532 해커같은 애미 뒤진 련 빼고 암살자나 쳐 내지 ㅇㅇ(220.122) 22.12.18 153 0
531 야 늬들은 게임잡에 포폴 올리고 스카웃 신청 받은 적 있냐? [1] ㅇㅇ(1.241) 22.11.23 336 0
530 요즘 펄어비스 내부어떠냐 아직도 개씨발이냐? [2] ㅇㅇ(118.235) 22.11.18 437 0
529 uv맵 좀 징그럽지 않음 ? ㅇㅇ(58.141) 22.10.13 163 0
526 기획 관련 질문입니다 [1] ㄱㄱ(128.134) 22.08.20 344 0
525 언리얼엔진5 루멘 라이팅의 퍼포먼스를 보여주는 해외 작품. 슬기로운스캔생활갤로그로 이동합니다. 22.08.13 189 0
522 갤 사람이 거의 없나? [1] 도기갤로그로 이동합니다. 22.06.04 354 0
518 이런 갤이 있었네 [1] ㅇㅇ(49.142) 22.03.18 299 0
517 혹시 만든 겜 홍보해도돼? [6] 엠제이마크갤로그로 이동합니다. 22.03.10 872 7
515 메타버스플랫폼 기획서때문에 예시를 만들어야하는데 사용할만한 툴 있을까요? [1] ㅇㅇ(122.34) 22.02.27 415 0
514 주딱 머함? ㅇㅇ(124.197) 22.01.16 211 0
513 창세기전 > 파판 > 원신 철학이 없는 차이나치 게임 ㅇㅇ(121.160) 22.01.02 239 0
512 보이스 게임 만들어보신 분 ㅠㅠ [1] ㅇㅇ(110.9) 22.01.02 142 0
511 여기 유익한 자료 엄청 많구나 ㅇㅇ(182.213) 21.12.22 306 0
510 이거 뭘까요?? ㅇㅇㅋㅋ(220.70) 21.12.06 307 0
509 여기 게임 디자인 관련 갤러리인가요? 아니면 게임 개발에 관련된 것? [1] 팡팡갤로그로 이동합니다. 21.11.19 614 0
508 혹시 이거 서로 같은 거야? ㅇㅇ(14.33) 21.11.06 201 0
507 이렇게 양질의 정보가 가득한 갤이 있다니 ㅇㅇ(106.102) 21.10.23 295 1
505 번역) 벤 브로드 - 게임 디자이너가 되는 방법 [2] 취업전선갤로그로 이동합니다. 21.08.03 945 7
504 번역) 벤 브로드 - 게임 업계에서 직업을 갖는 방법 취업전선갤로그로 이동합니다. 21.08.03 400 2
503 번역) The Door Problem: 직군별 문에 대한 문제 접근법 취업전선갤로그로 이동합니다. 21.08.02 332 4
502 안녕하세요 간만에 와서 인사글 써봅니다 ㅇㅇ(121.170) 21.07.22 204 2
501 언제 코드를 다시 써야 하는가. Dunkirka갤로그로 이동합니다. 21.07.02 272 0
500 온라인 게임 개발자가 Aim Lab에 관심을 가지는 이유 Dunkirka갤로그로 이동합니다. 21.07.02 419 0
499 생각한 것보다 까다로운 게임 현지화 Dunkirka갤로그로 이동합니다. 21.06.28 399 0
497 게임 디자인 과학이 중요한 이유 Dunkirka갤로그로 이동합니다. 21.06.14 744 3
496 개발사와 기획서 Dunkirka갤로그로 이동합니다. 21.06.14 365 0
495 게임 순위 차트 살펴보기 Dunkirka갤로그로 이동합니다. 21.06.07 432 0
494 게임 스토어 페이지의 '정보' 섹션을 개선하는 방법 Dunkirka갤로그로 이동합니다. 21.06.04 204 0
493 플레이어 보상하기 Dunkirka갤로그로 이동합니다. 21.05.30 480 0
492 다잉 라이트의 자연적인 이동 시스템 Dunkirka갤로그로 이동합니다. 21.05.30 276 1
491 튜토리얼 이후 플레이어 잡아두기 Dunkirka갤로그로 이동합니다. 21.05.27 264 0
490 마야 vs 블렌더 [1] Dunkirka갤로그로 이동합니다. 21.05.26 6119 1
생산성을 높이는 점진적 프로그래밍 Dunkirka갤로그로 이동합니다. 21.05.26 446 0
487 야근 안 하고 라이브 개발하기 Dunkirka갤로그로 이동합니다. 21.05.25 272 0
486 인터페이스와 스토리텔링 Dunkirka갤로그로 이동합니다. 21.05.25 363 0
485 게임 경제 디자인의 기초 Dunkirka갤로그로 이동합니다. 21.05.25 337 0
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2