메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

유지보수가 어렵게 코딩하는 방법(3) : 평생을 개발자로 먹고 살 수 있다 ;-)

한빛미디어

|

2011-07-12

|

by HANBIT

21,602

저자 : 로에디 그린(Roedy Green)
원문 : Canadian Mind Products

이전기사

문서화

진실을 말하는 것은 쉽다. 그러나 거짓말을 잘 하려면 센스가 필요하다.
- 사무엘 버틀러(Samuel Butler) (1835 - 1902)

종종 잘못된 문서화는 아예 문서화를 하지 않은 것보다 더 나쁜 결과를 초래한다.
- 베르트랑 메이어(Bertrand Meyer)

컴퓨터는 주석과 문서화 부분은 무시한다. 따라서 온 힘을 기울여 주석과 문서화를 활용한다면 불쌍한 유지보수 프로그래머를 좌절시킬 수 있을 것이다.

주석에 거짓말을 추가하라

적극적으로 거짓말을 할 필요는 없다. 그냥 자연스럽게 주석을 업데이트 하지 않아 내용이 맞지 않는 것처럼 보이게 하자.

명백한 사실을 문서화하라

코드에 /* add 1 to i */와 같은 양념을 추가한다. 중요한 점은 패키지나 메소드의 전체 목적과 같은 어려운 부분은 절대 문서화하지 않는다는 사실이다.

이유는 빼고 어떻게에 대해서만 문서화하라

프로그램이 무엇을 하는지에 대한 세부 사항 그리고 프로그램이 무엇을 달성하지 않는 것인지에 대해 문서화하라. 버그가 생기면 수정을 담당하는 프로그래머는 해당 코드가 무엇을 수행해야 하는지 알 수 없게 된다.

"명백하게" 문서화하지 말아라

예를 들어, 항공기 예약 시스템을 구현하고 있는데 다른 항공편을 추가하려면 25 군대를 수정해야 한다고 가정하자. 물론 어디를 수정해야 할지를 문서화하면 안 된다. 나중에 누군가 우리 코드를 수정하려면 전체 라인을 완벽하게 이해해야만 원하는 수정을 할 수 있을 것이다.

문서화 템플릿의 적절한 활용

함수 문서화 프로토타입을 이용해 자동으로 코드에 문서화 틀을 제공할 수 있다. 이 때 다른 함수(혹은 메소드나 클래스)에서 복사해서 사용하고 절대 필드에는 문서화 틀을 사용하지 말아야 한다. 어쩔 수 없이 필드에 문서화 프로토타입을 사용해야 한다면 모든 함수에서 같은 파라미터 이름을 사용하도록 하고 주의사항도 같게 하자. 물론 이 주의사항이 현재 함수와 전혀 관련이 없는 것일수록 좋겠다.

디자인 문서의 적절한 활용

상당히 복잡한 알고리즘을 구현해야 할 때에는 코딩을 하기 전에 디자인을 확실하게 해야 한다는 고전 소프트웨어 엔지니어링 원칙을 지켜야 한다. 상당히 복잡한 알고리즘의 각 단계에 대한 설명을 포함할 수 있도록 매우 세밀하게 디자인 문서를 작성한다. 이 문서를 세부적으로 만들수록 좋다.

디자인 문서에서 알고리즘을 구조화된 여러 단계로 나눠서 각 문단에 자동으로 계층적인 번호를 추가할 수 있다. 헤딩은 적어도 5단계로 만들자. 가능하면 구조를 잘게 나눔으로써 최종 결과물이 나왔을 때에 500개가 넘는 문단에 자동으로 번호를 추가할 정도가 돼야 한다. 다음은 실생활에 사용하는 어느 문단의 예다. 1.2.4.6.3.13 - 선택한 마이그레이션을 적용했을 때 발생하는 모든 효과를 표시하라 (간단한 의사코드는 생략).

그리고...(이제 본격적인 혼란의 세계로 빠져든다) 코드를 작성할 때에는 각 문단에 대응하는 전역 함수를 만든다

Act1_2_4_6_3_13() 
디자인 문서에 나와있으므로 위 함수에 대한 문서화는 따로 필요치 않다!

디자인 문서의 번호는 자동으로 매겨지는 것이므로 변경이 발생했을 때 이를 일일이 코드에 반영하는 것은 정말 어렵다(함수 명은 자동으로 변경되는 것이 아니므로). 그럼 큰일 아닌가? 우리는 문서를 최신으로 유지하지 않으면 되므로 걱정할 것 없다. 오히려 문서 추적에 필요한 사항을 모두 파괴하는 것이 좋다.

운이 좋다면, 우리의 뒤를 이어 작업할 사람은 지금은 멸종한 286 컴퓨터와 먼지가 수북이 쌓여있는 창고 선반 뒤에 숨겨진 앞뒤가 맞지 않는 초안 한 두 개를 건질 수 있을 것이다.

측정 단위

피트, 미터, 통과 같은 측정 단위를 변수, 입력, 출력, 매개변수에 문서화는 절대 하지 않는다. 이는 엔지니어링 작업에서 매우 중요한 요소다. 마찬가지로 변환 상수의 측정 단위나 값이 어떻게 전달되는지 등도 문서화하지 않는다. 주석에 잘못된 측정 단위를 슬쩍 넣는 것은 유치하지만 효과적인 방법이다. 좀더 사악한 방법을 원한다면 자기 자신만의 측정 단위를 만들어 보는 방법도 있다. 자신의 이름이나 다른 아무개 이름을 사용하고 해당 단위에 대해 정의하지 않는다. 누군가가 우리의 작업 방식에 이의를 제기한다면 소수점 연산보다 정수 연산을 잘 할 수 있도록 그렇게 한 것이라는 등의 동문서답을 이용하자.

문제점

코드의 문제점을 문서화하지 않는다. 클래스에 버그가 있을 수 있다는 사실을 발견했으면 혼자만의 비밀로 간직한다. 코드를 어떻게 재조직하거나 재작성해야 할지 아이디어가 떠올랐을지라도 문서로 남겨놓지 않는다. 영화 밤비(Bambi)에서 귀염둥이 썸퍼(Thumper)의 말을 기억하자. "좋은 말을 하지 않으려거든, 그 입 닫으라". 코드를 만든 프로그래머가 우리의 주석을 보고 어떻게 생각하겠는가? 회사 사장이 본다면? 고객이 본다면? 심지어 해고될 수 있다. "수정해야 함!"이라는 익명의 주석을 활용할 수 있다. 특히 이 주석이 어느 부분을 가리키는 것인지 명확하지 않을수록 좋다. 내용을 되도록 흐지부지하게 만들어서 누군가를 비난하고 있다는 느낌이 들지 않도록 주의해야 한다.

변수 문서화

변수 선언에는 절대로 주석을 달지 않는다. 변수를 어떻게 사용해야 하고, 경계 값은 무엇이며, 사용할 수 있는 값은 무엇이고, 함축된/표시된 십진수 점, 측정 단위, 표현 형식, 데이터 입력 규칙(전체 값을 채워야 하는지, 반드시 입력해야 하는 지와 같은), 값을 신뢰할 수 있는 조건 등과 같은 정보는 코드를 통해 충분히 얻을 수 있다. 주석을 기록하도록 상사가 강요한다면 메소드 바디에 변수를 넣어서 사용하자. 그러나 이 경우에도 절대 임시로라도 변수 선언에 주석을 추가하면 안된다.

폄하하는 말을 주석에 사용하기

외부 회사와 유지보수 계약을 체결할 수 없도록 다른 소프트웨어 선도 업체를 폄하하는 글을 추가한다. 특히 현재 회사와 계약할 수 있는 가능성이 있는 회사를 공격할수록 좋다. 예를 들면, 다음과 같다.

/* 내부 루프 최적화 
Software Services Inc.,의 굼뱅이들은 아래와 같은 코드를 꿈에도 몰랐겠지.
그 녀석들은 아마 답답한 의 기능을 이용해서 50배나 느리고 메모리도 많이 사용했을 꺼야.
*/ 
class clever_SSInc 
    { 
    .. . 
    } 
가능하다면 주석 뿐만 아니라 코드 구문상 중요한 부분에 모욕적인 발언을 추가하는 것이 좋다. 그러면 나중에 유지보수가 필요할 때 관리자는 해당 코드를 제거하려고 애쓸 것이다.

펀치 카드의 코볼(CØBØL)을 사용한 것처럼 주석을 추가하라

개발 환경 분야의 발전 특히, SCID 등의 사용을 거부하라. 모든 함수와 변수 선언이 한번의 클릭으로 가능할 것이라는 헛소문에 현혹되지 말자. 비주얼 스튜디어 6.0으로 개발한 코드는 edlin이나 vi를 사용하는 개발자가 유지보수 할 것이라 가정하자. 드라콘 식의 주석 규칙을 따르는 것이 소스 코드를 올바로 작성하는 것이라는 신념을 갖자.

몬티 파이썬 주석

makeSnafucated라는 메소드에는 /* make snafucated */과 같은 자바독(JavaDoc) 주석을 추가한다. 어디에도 snafucated의 의미를 정의하지 않는 것이 핵심이다. snafucated의 의미를 모르는 사람은 바보임에 틀림없다. Sun AWD 자바독에는 이와 같은 기법을 사용한 고전 예제가 많다.

프로그램 디자인

유지보수 할 수 없는 코드를 작성하는 기본 규칙은 가능한 한 여러 장소에 가능한 다양한 방법으로 사실을 기록하는 것이다.
- 로에디 그린

유지보수가 쉬운 코드를 작성의 핵심 요소는 응용 프로그램의 각 요소를 한 곳에 정의하는 것이다. 바꾸어서 생각해보면 우리가 수정해야 할 코드가 한 곳에 모여있음을 의미한다. 이렇게 하면 수정을 하더라도 전체 프로그램 수행에 영향을 최소화할 수 있다. 즉, 유지보수가 어려운 코드를 만들려면 요소를 반복적으로 가능한 한 여러 장소에 기술해야 한다. 다행히도 자바와 같은 언어로 이와 같이 유지보수가 어려운 코드를 비교적 쉽게 작성할 수 있다. 예를 들어, 폭넓게 사용하는 변수는 여러 가지 형 변환 및 변환을 거치고 있으며, 관련 형식의 임시 변수가 사용되고 있을 가능성이 크므로 형식을 변환하기가 쉽지 않다. 더욱이 화면에 뭔가를 출력하는 변수일 경우라면 출력과 데이터 입력 관련 코드를 수동으로 수정해야 한다. C와 자바를 포함한 Algol 계층 언어는 데이터를 배열, 해시테이블, 파일, 데이터베이스에 저장하는 문법이 완전 다르다. Abundance와 같은 언어나 Smalltalk 확장 언어에서는 데이터 저장 문법은 같고 선언만 다르다. 따라서 자바의 부족한 기능을 공략하자. 현재 RAM으로 감당할 수 없이 크기가 커질 데이터를 배열로 저장하자. 그러면 유지보수 프로그래머는 나중에 배열을 파일로 바꿔야만 하는 악몽같은 작업을 피할 수 없을 것이다. 마찬가지로 데이터베이스에 작은 파일을 사용하자. 그러면 유지보수 프로그래머는 성능 최적화 때 해당 파일을 배열 접속 방식으로 바꿔야 하는 즐거운 경험을 맛볼 것이다.

자바 형변환

자바의 형변환 스킴은 하느님의 귀중한 선물이다. 형변환은 언어에서 필요한 기능이므로 이를 아무 거리낌없이 남용할 수 있어야 한다. Collection에서 오브젝트를 가져왔으면 원래 형식으로 형변환시켜야 한다. 어떤 때는 변수 형식 종류가 수십이 넘기도 한다. 나중에 데이터의 형식을 바꾸려면 모든 형변환도 바꿔야 한다. 운이 없는 유지보수 프로그래머가 모든 형변환을 적절하게 처리하지 못한 경우(혹은 너무 많은 변환을 한 경우) 컴파일러가 그 사실을 알려줄 수도 있지만, 그렇지 못한 경우도 있다. 마찬가지로 변수 형식이 short에서 int로 변경하면 관련 형변환도 모두 (short)에서 (int)로 바꿔야 한다. 일반 캐스트 연산자인 (cast)와 일반 변환 연산자 (convert)라는 새로운 연산자에 대한 필요성이 논의되고 있다. 이들 연산자는 변수 형식이 변경되어도 유지보수 할 필요성이 없게 해주는 새로운 연산자가 될 전망이다. 이런 이단적인 연산자가 언어 스펙에 포함되게 보고만 있어야 하는가? RFE 114691에서 형변환의 필요성을 제거하기 위한 genericity 부분에 적극 투표하길 바란다.

자바의 중복성 남용하기

자바에서는 모든 형식을 두 번 지정해야 한다. 자바 프로그래머는 이러한 관습에 익숙하기 때문에 두 형식을 아래처럼 살짝 바꾸어 놓아도 눈치챌 수 있는 사람은 많지 않다.

Bubblegum b = new Bubblegom(); 
불행히도 ++ 연산자의 대중성 때문에 다음과 같은 의사 중복 코드를 성공시키기가 쉽지 않다.
swimmer = swimner + 1; 

검증을 멀리하라

입력 데이터에 대한 어떤 종류의 불일치 검사나 정확성 검사를 수행하지 않는다. 즉, 우리는 회사 장비를 온전히 신뢰하고 있으며 모든 프로젝트 파트너와 시스템 오퍼레이터를 신뢰하는 완벽한 팀원임을 보여줄 수 있다. 입력 데이터가 이상하거나 문제가 있는 듯 보이더라도 항상 합리적인 값을 반환하기 위해 노력해야 한다.

예의를 지키고 무턱대고 주장(Assert)하는 일을 피하라

assert() 메커니즘은 3일짜리 버그 축제를 10분짜리로 만들어 버릴 수 있으므로 피해야 한다.

캡슐화를 멀리하라

효율성 측면을 고려할 때 캡슐화를 멀리해야 한다. 메소드 호출자는 메소드가 어떻게 동작하는지를 알 권리가 있다.

복사하고 수정하라

효율성이라는 명목으로 잘라내기/붙이기/복사하기/수정하기를 남발하자. 이 방식은 작은 재사용 가능한 모듈 여럿을 사용하는 것보다 실행 속도가 빠르다는 장점이 있다. 특히 이 방식은 우리가 작성하는 코드 라인 수를 업무 진행 척도로 여기는 곳에서 일할 때 유용하다.

정적 배열을 사용하라

라이브러리의 모듈에 이미지를 저장할 배열이 필요한 경우에는 정적 배열을 선언해야 한다. 아무도 512 x 512 크기 이상의 이미지를 사용하지 않을 것이므로 크기가 고정된 배열도 좋다. 정확성을 높일 수 있도록 double 배열을 사용하는 것도 바람직하다. 이렇게 하면, 2 메가 크기의 정적 배열을 효과적으로 숨길 수 있다. 클라이언트는 우리가 만든 루틴을 한번도 수행하지 않았음에도 미친 것처럼 허우적대고 프로그램은 결국 클라이언트의 메모리를 초과할 것이다.

더미 인터페이스

"WrittenByMe"와 같은 빈 인터페이스를 만들고 모든 클래스에서 "WrittenByMe" 인터페이스를 구현하도록 하자. 그리고 우리가 사용하는 자바의 내장 클래스 래퍼 클래스를 만들자. 핵심은 우리 프로그램의 모든 오브젝트가 위 인터페이스를 구현하게 하는 것이다. 마지막으로 모든 메소드를 직접 구현하므로 매개변수와 반환 형식을 WrittenByMe가 구현하는 것이 목적이다. 이 기법을 사용하면 특정 메소드가 어떤 작업을 수행하는지 알아내기 어렵게 할 수 있고 아주 다양한 종류의 형변환 이라는 즐거움을 만끽할 수 있다. 이를 좀 더 활용해서 각 팀 멤버에게 자신만의 개인 인터페이스(예를 들어, WrittenByJoe)를 만들어줄 수 있다. 그리고 각자가 작업한 클래스는 자신만의 인터페이스를 구현하게 한다. 자 이제 의미 없는 수많은 인터페이스 중에 아무 인터페이스를 골라잡아 오브젝트 참조에 사용할 수 있다.

거대 리스너

각 컴포넌트에 리스너를 개별적으로 만들지 않는다. 우리 프로젝트의 모든 버튼의 이벤트를 처리할 리스너를 단 한 개로 통일하고 수많은 if…else문으로 각 버튼 동작을 처리하는 것이 바람직하다.

좋은 것™은 남용하라

캡슐화와 oo를 남용하라. 예를 들면,

myPanel.add( getMyButton() ); 
private JButton getMyButton() 
      { 
      return myButton; 
      } 
위 코드에 특별히 흥미로운 부분은 없어 보인다. 걱정할 필요 없다. 언젠가는 재미있는 일이 일어날 것이다.

우호적인 친구

C++에서는 가능한 한 자주 friend-선언을 사용한다. 생성된 클래스의 클래스 생성 포인터 처리와 결합하는 것도 좋은 방법이다. 그럼 인터페이스를 생각하는데 시간을 낭비할 필요성이 없어진다. 또한 private와 protected 키워드를 사용해 클래스를 캡슐화 할 수 있다.

삼차원 배열을 사용하라

삼차원 배열을 적극 사용하자. arrayA의 행 데이터를 arrayB의 열에 채우기와 같이 배열간의 데이터를 이동할 때는 복잡한 방법을 사용할수록 좋다. 특별한 이유 없이 오프셋을 0이 아닌 1로 사용한다면 유지보수 프로그래머를 불안하게 만들 수 있다.

혼합과 매치

가능하면 accessor 메소드와 public 변수를 함께 사용한다. 이 방식을 사용하면 accessor를 호출하지 않고도 오브젝트 변수를 변경할 수 있다. 그러나 여전히 우리 클래스는 "자바 빈"이라고 말할 수 있다. 이 방법은 누가 변수 값을 변경하는지 알아내려고 로깅 기능을 추가한 유지보수 프로그래머를 좌절시킬 수 있다는 장점도 제공한다.

감싸고, 감싸고, 감싸라

우리가 구현하지 않은 코드를 우리 메소드에 사용해야 할 때에는 다른 더러운 코드에 우리 코드가 오염되지 않도록 적어도 한 번 이상 래퍼 레이어를 사용해야 한다. 어쩌면 다른 부분의 저자도 언젠가 모든 메소드의 이름을 자기 마음대로 바꾸어 버릴지 모를 일이다. 그럼 우리는 어떻게 대처해야 할까? 이러한 경우 래퍼를 만들어 우리 코드를 보호하거나 VAJ가 전체적인 이름 변경을 처리하게 할 수 있다. 한편 이는 간접적으로 래퍼 레이어를 통해 어떤 멍청한 일을 저지르기 전에 그를 제거할 구실을 제공하는 기회로 삼을 수 있다. 자바의 주요 문제점 가운데 하나는 별다른 작업을 수행하지 않고 다른 메소드의 같은 이름 또는 밀접히 연관된 이름을 호출하는 더미 래퍼 메소드 없이는 간단한 문제도 해결하기 힘들다는 점이다. 즉, 우리는 아무 작업도 하지 않는 4단계의 래퍼를 쥐도 새도 모르게 만들 수 있다. 소스 코드 혼잡성을 극대화하려면 각 단계에서 메소드 이름을 변경하고, 랜덤으로 유의어 사전에서 동의어를 선택할 수 있다. 이와 같은 방법을 이용해 마치 뭔가가 일어나고 있다는 환상을 심어줄 수 있다. 나중에 이름을 변경하면서 프로젝트 용어의 일관성을 깨뜨릴 가능성도 커진다. 코드에서 래퍼의 각 단계를 건너뛰도록 해둠으로써 혹시라도 우리가 만든 여러 단계를 제거하려는 시도를 방지할 수 있다.

감싸고 감싸고 감싸고 더 감싸라

모든 API 함수를 적어도 6~8번은 감싸야 하고 다른 소스 파일에서 함수를 정의해야 한다. #define으로 이들 함수를 연결하는 것도 좋은 방법이다.

비밀은 없다!

언젠가 모두가 사용할 수 있도록 모든 메소드와 변수를 public으로 선언하라. 메소드를 public으로 선언한 이후에는 메소드 기능을 축소하기가 어렵다. 즉, 내부 동작을 수정하기가 매우 어려워진다. 이 기법을 사용하면 클래스의 목적이 무엇인지를 알기 어렵게 하는 부수적 효과를 얻을 수 있다. 상사가 우리에게 미친 것 아니냐고 얘기한다면, 우리는 그저 투명한 인터페이스라는 고전 원칙을 따르고 있을 뿐이라고 변명하면 된다.

카마수트라

이 기술로는 유지보수 프로그래머 뿐만 아니라 사용자와 문서화 담당자의 집중을 방해하는 효과를 발휘할 수 있다. 같은 메소드에 수십 개 이상의 오버로드를 이용한 변형을 생성해 약간만 다른 기능을 하게 만든다. 소설가 오스카 와일드는 카마수트라의 47과 115번 자세에서 115번의 여자의 중지와 인지 손가락을 포갠 것을 제외하면 같은 자세라는 사실을 발견한 것 같다. 패키지 사용자가 여러 변형 버전 가운데 어떤 것을 사용해야 할지 선택하려면, 먼저 길고 긴 메소드 목록을 정독해야 한다. 동시에 문서화해야 할 양도 크게 늘어나기 때문에 문서를 최신으로 유지하기 힘들게 된다. 상사가 우리에게 왜 이런 짓을 하는 것이냐고 묻는다면, 사용자의 편의성을 개선하기 위해서라고 설명하자. 공통 로직을 복제한 다음 복사본의 내용이 더 이상 동기화되지 않을 때까지 기다리면 효과가 더욱 극대화 된다.

치환으로 당황시키기

drawRectangle(height, width)라는 메소드가 있다면 다른 부분은 건드리지 말고 파라미터 순서만 drawRectangle(width, height)처럼 역순으로 바꿔보자. 그리고 몇 번의 릴리즈가 일어난 다음에 원상 복귀시킨다. 유지보수 프로그래머는 그런 변경이 일어났는지 쉽게 구별하기가 어려울 것이다. 일반화 문제는 독자 여러분에게 숙제로 남겨둔다.

테마와 변형

하나의 메소드에 파라미터를 사용하기 보다는 되도록이면 여러 메소드를 만드는 것이 좋다. 예를 들어 왼쪽, 오른쪽, 가운데 정렬을 의미하는 상수를 파라미터로 넘겨줄 수 있는 setAlignment(int alignment)보다는 setLeftAlignment, setRightAlignment, setCenterAlignment와 같이 세 개의 메소드를 정의하는 것이 바람직하다. 마찬가지로 공통 로직을 복제해서 동기화를 어렵게 만들면 효과가 커진다.

Static이 좋다

가능하다면 변수를 static으로 만들자. 우리 프로그램에서 클래스 인스턴스가 한 개 이상 필요하지 않으면 다른 이들도 마찬가지일 것이다. 프로젝트의 다른 코더가 이에 대해 불평한다면 이 방법 덕분에 실행속도가 빨라졌음을 알려주자.

카길사의 진태양난

카길사의 진퇴양난을 이용하자. "적절한 수준의 부정 수단을 이용한다면 모든 디자인 문제를 해결할 수 있다". 프로그램 상태를 갱신하는 메소드를 찾을 수 없을 때까지 OO 프로그램을 분해하자. 모든 결과물을 전체 시스템 내에서 사용하는 모든 함수 포인터를 포함하는 포인터 포레스트를 탐색한 결과에 대한 콜백으로 활성화 할 수 있게 정렬하면 더욱 좋다. 포레스트 탐색 정렬을 활성화하면 깊은 복사(실제로는 그렇게 깊지 않지만)해서 만들어진 오브젝트 레퍼런스를 해제하는 과정에서 부작용을 일으키게 할 수 있다.

잡동사니 수집

사용하지 않고 오래된 메소드나 변수라 할지라도 모두 코드에 모아준다. 1976년에 한번 사용했던 적이 있는 코드라도 언제 어떻게 사용해야 할지 누가 알겠는가? 항상 프로그램의 변경 사항을 관리하므로 "바퀴를 다시 발명하는 일은 피해"(상사는 이런 말을 좋아한다)야 한다. 메소드와 변수 주석을 수수께끼처럼 남겨둔다면, 그 코드를 유지보수해야 할 누군가는 코드를 보고 겁부터 집어먹을 것이다.

Final이 주는 즐거움

모든 최종 클래스를 final로 정의하라. 이렇게 프로젝트를 마무리할 수 있다. 즉 아무도 우리가 만든 클래스를 확장해 작업을 개선시킬 수 없다. 누군가가 우리 클래스를 확장하면서 발생하는 보안 취약성도 방지할 수 있다. 마찬가지로 java.lang.String이 final인 이유와 같은 맥락이다. 프로젝트의 다른 코드가 불평한다면 언제나처럼 이 기법으로 인해 얻고 있는 속도 향상에 대해 얘기해주면 된다.

인터페이스를 피하라

자바에서 인터페이스는 무시해야 할 존재다. 관리자가 이에 대해 불평한다면 자바의 인터페이스는 우리로 하여금 같은 인터페이스를 구현하는 다른 클래스에서 "잘라내고 붙이기"를 반복하게 만드는 존재이며 이는 당신도 알다시피 유지보수를 어렵게 하는 것이라고 말하자. 인터페이스 대신 자바 AWT 설계자의 방식을 따를 수 있다. AWT 설계자는 특정 클래스와 해당 클래스를 상속하는 클래스에서만 사용할 수 있는 많은 기능을 추가하고 각 메소드에서는 "instanceof"를 수시로 활용했다. 누군가가 우리 코드를 재사용하고 싶어한다면 우리 클래스를 상속받도록 할 수 있다. 혹시 두 개의 다른 클래스를 모두 재사용하고 싶어하는 이가 있다면, 불행히도 그렇게 하는 것은 불가능하다! 어쩔 수 없이 인터페이스가 필요한 상황이라면 "ImplementableIface"와 같은 하나의 인터페이스를 만들어 다용도로 사용할 수 있게 해야 한다. 인터페이스를 구현하는 클래스명에 "Impl"을 붙이는 것은 학계에서 떠도는 유행 중 하나다. Runnable을 구현하는 클래스 등에서 유용성이 커진다.

레이아웃을 피하라

절대 레이아웃을 이용하지 말라. 그러면 유지보수 프로그래머가 필드 하나를 추가하려고 화면에 나타나는 모든 다른 콤포넌트의 좌표를 일일이 수정하게 만들 수 있다. 상사가 레이아웃을 사용하도록 강요한다면, 거대한 단일 GridBagLayout을 만들어서 절대 그리드 좌표로 하드 코딩하는 방법으로 우회하자.

환경 변수

다른 프로그래머가 사용할 클래스를 만들어야 한다면 환경 변수를 확인하는 코드(C++에서는 getenv(), 자바에서는 System.getProperty())를 클래스의 정적 초기화를 담당하는 메소드(이름없는)에 추가하자. 이러한 방법으로 모든 매개변수를 생성자를 거치지 않고 클래스에 전달할 수 있다. 초기화 담당 메소드를 사용하면 프로그램 바이너리를 로드하는 순간 호출된다는 장점이 있다. 즉, 프로그램의 main()이 호출되기도 전에 이와 같은 작업이 완료된다. 따라서 우리의 클래스를 직접 확인하기 전까지는 프로그램의 다른 부분에서는 파라미터 값을 고칠 방법이 없다. 오히려 사용자가 우리의 방식에 맞춰 자신의 환경 변수를 모두 설정하는 편이 편할 것이다.

테이블 기반 로직

테이블 기반 로직은 피해야 한다. 테이블 기반 로직은 너무 훤히 들여다보여서 최종 사용자는 바로 교정을 시작할 수 있고 곧 몸서리 칠 수 있다. 심지어는 직접 테이블을 수정할 수도 있다.

엄마의 필드를 수정하라

모든 기본형 파라미터는 값으로 전달되므로 자바에서는 이들을 읽기-전용으로 전달한다. 피호출자에서 파라미터 값을 수정할 수는 있지만, 호출자의 변수에는 영향을 미치지 않는다. 반대로 넘겨지는 모든 오브젝트는 읽고 쓸 수 있다. 레퍼런스는 값으로, 즉 오브젝트 자체는 레퍼런스로 전달된다. 피호출자는 오브젝트에 원하는 모든 동작을 수행할 수 있다. 메소드에서 넘겨진 파라미터의 각 필드를 수정하는지 여부를 절대 문서화하지 말자. 메소드에서 각 필드를 수정하는 경우에는 메소드 이름을 마치 보기만 할 것처럼 보이는 이름으로 위장해야 한다.

전역 변수의 마술

예외를 이용해 예외를 처리하는 것보다는 자신만의 에러 메시지 루틴 집합을 전역 변수로 갖는 것이 좋다. 시스템에서 오랫동안 수행되는 모든 루프에다 전역 플래그를 검사하고 문제발생시 종료하도록 코드를 추가하자. 사용자가 ‘reset’ 버튼을 눌렀을 때 이를 알릴 수 있는 또 다른 전역 변수도 추가한다. 마찬가지로 시스템의 모든 루프에 이 두 번째 전역 변수를 확인하는 코드를 넣어야 한다. 이 때 요청시에도 종료하지 않는 루프 몇 개를 숨겨두는 것을 잊지 말자.

아무리 강조해도 지나치지 않을 그대 이름은 전역!

우리가 지역 변수를 사용하는 것을 원치 않았다면 신은 전역 변수라는 것을 창조하지 않았을 것이다. 따라서 가능한 한 많은 수의 지역 변수를 사용하므로 신을 실망시키지 말아야 한다. 특별한 이유가 없더라도 각 함수에서는 최소한 두 개 이상의 전역 변수를 사용해야 한다. 좋은 유지보수 프로그래머라면 이것이 일종의 검출 연습이라는 사실을 금방 깨닫게 될 것이다. 그리고 그는 진심을 다하는 유지보수 프로그래머와 취미로 하는 유지보수 프로그래머를 구별해주는 이 테스트를 즐길 것이다.

여러분! 전역에 대해 한 번 더 살펴봅시다

전역 변수를 사용하면 함수에서 매개변수를 지정하는 일을 생략할 수 있다. 이를 적극 활용하자. 전역 변수 중 몇 개를 선택해서 어떤 프로세스가 작업을 수행할지 지정하자. 유지보수 프로그래머는 바보처럼 C 함수에서 부작용이 발생하진 않을 것이라 생각할 수 있다. 이들에게 깜짝 놀랄 결과를 보여주자. 물론 내부 상태 정보는 지역 변수로 사라진다.

부작용

C에서 함수는 값이 변하지 않는 것(부작용 없이)으로 알려져있다. 이 정도면 충분한 힌트가 되겠는가?

철회

루프의 바디에서는 루프가 성공적으로 수행되고 있으며 모든 포인터 변수 값이 즉시 갱신되고 있다고 가정하라. 루프 동작 중에 예외가 발생한 경우에는 루프 바디 다음 부분에서 조건문을 이용해 포인터 값을 철회하도록 한다.

지역 변수

쓰고 싶은 마음이 굴뚝같이 들더라도 절대 지역 변수를 사용하지 말라. 자유롭게 클래스의 다른 메소드에서 공유할 수 있도록 하는 것보다는 인스턴스 변수나 static 변수로 만드는 것이 바람직하다. 이렇게 함으로 다음에 비슷한 정의를 갖는 다른 메소드를 작업할 때에는 시간을 절약할 수 있을 것이다.

줄이고, 재사용하고, 재활용하라

콜백(callback)에 사용할 데이터를 저장할 구조체를 정의해야 한다면, 그 구조체를 PRIVDATA라고 부르자. 모든 모듈은 자신만의 PRIVDATA를 정의할 수 있다. 이 구조체로 VC++의 디버거를 교란시킬 수 있다. 변수 watch 윈도우에 PRIVDATA 변수가 있는 상태에서 해당 변수를 펼치려고 하면 디버거는 어느 PRIVDATA를 의미하는 것인지 결정할 수 없어 아무것이나 선택한다.

설정 파일

설정 파일에는 보통 키워드=값 형태의 데이터를 저장한다. 프로그램을 로드할 때 이 값을 자바 변수로 읽어온다. 키워드와 자바 변수의 이름을 살짝 다르게 하는 방법으로 혼란을 일으킬 수 있다. 심지어 실행 중에 값이 변경되지 않는 상수도 설정 파일을 이용하자. 파라미터 파일 변수를 유지보수 하려면 간단한 변수를 유지보수 할 때보다 다섯 배나 많은 코드가 필요하다.

퉁퉁 부은 클래스

중요하지 않고 잘 알려지지 않은 메소드와 속성을 모든 클래스에 포함시킴으로써 외부에 둔감한 클래스를 만들 수 있다. 일례로, 천체 기하학적 궤도를 정의하는 클래스에 해수면 조류 스케쥴과 크레인(Crane) 날씨 모델을 구성하는 속성을 포함할 수 있다. 이와 같은 기법으로 클래스에 수많은 기능을 정의할 수 있을 뿐만 아니라 시스템에서 필요한 부분을 검색하는 작업을 서울에서 김서방 찾기처럼 힘들게 만들 수 있다.

자식 클래스에게 양보하는 미덕

객체 지향 프로그래밍은 유지보수 할 수 없는 코드를 작성하도록 하늘이 준 선물이다. 클래스에 10개 프로퍼티(멤버/메소드)를 갖고 있다 가정하자. 베이스 클래스는 하나의 프로퍼티를 갖고 있고, 베이스 클래스를 상속하는 클래스에서 한 개씩만 속성을 추가하는 방식으로 9 단계를 상속받도록 클래스 계층을 구성할 수 있다. 가장 하위 클래스로 10개 프로퍼티 모두를 사용할 수 있다. 가능하다면 각 클래스 선언을 다른 파일에 정의하는 것이 좋다. 이렇게 함으로써 INCLUDE나 USES 구문을 팽창시키는 부수효과를 얻을 수 있고 유지보수 프로그래머는 자신의 편집기에 더 많은 파일을 열어보아야 한다. 물론 적어도 각 서브클래스의 인스턴스를 한 개 이상은 만들어야 함을 잊지 말자.

다음회 계속...

TAG :
댓글 입력
자료실