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

한빛출판네트워크

IT/모바일

Trax, 그만의 코딩이야기(2) - 주석의 길이

한빛미디어

|

2006-10-24

|

by HANBIT

14,307

제공: 한빛 네트워크
저자: 한동훈

주석에 대해서는 여러가지 의견이 있습니다. "주석을 아껴라"라는 의견도 있고, "주석을 사용하느냐 이용하느냐?"라는 의견도 있습니다.

보다 오래된 논쟁으로는 이런 글들도 있습니다.

주석문이 적어야 좋은 코드
http://python.kw.ac.kr:8080/python/bbs/readArticlepy?bbsid=freebbs&article_id=1397

이 글에 대해 리눅스 코리아의 이만용씨는 다음과 같은 글을 남깁니다.
http://python.kw.ac.kr:8080/python/bbs/readArticlepy?bbsid=freebbs&article_id=1397

XP로 잘 알려진 켄트 벡(Kent Beck)과의 인터뷰를 옮긴 분이자 국내에서 XP 전도사로 잘 알려진 김창준씨는 Kent Beck에서 "고수는 주석이 필요없는 코드를 만든다."라는 언급을 합니다. 다음과 같은 문구도 나옵니다.

"제 논지는, 주석이 필요없는 코드를 만드는 것은 오히려 어려울 수 있고, 이것을 위해 노력할 때가 그렇지 않을 때보다 더 가독성이 높은 코드가 나올 수 있다는 것입니다. 주석을 무조건 쓰지 말자는 것은 아닙니다"

주석의 사용에 대해서는 굉장히 많은 논의가 있으며, 지금 이 글을 읽고 있는 사람들도 굉장히 다양한 관점을 갖고 있을 겁니다. 나의 관점은 주석을 사용하거나 말거나 관계없다. 입니다. 대신에 코드와 주석을 포함해서 내용을 소설처럼 쉽게 읽을 수 있어야 합니다. 주석은 없고 코드만으로 구성되어 있는데 읽히지 않는다면 코드에 문제가 있습니다. 이런 것을 "숨겨진 의도" 또는 "숨겨진 지식"이라 부릅니다. 마찬가지로 주석과 코드가 있는데도 읽기가 어렵다면 잘못된 것입니다. 아마도, 주석과 코드 둘 다에게 잘못이 있는 겁니다.

나의 관점은 주석과 코드를 모두 동원해서 쉽게 읽힐 수 있으면 된다라고 했습니다. 여기서는 이를 설명하기 위해 "숨겨진 의도" 또는 "숨겨진 지식"을 가진 경우와 "주석문의 길이는 어느 정도가 적당한가?"에 대해 살펴보겠습니다.

숨겨진 의도

다른 사람이 작성한 코드를 읽을 때 읽기가 어려운 이유는 내가 그 사람의 사고 방식과 전혀 다른 인간이기 때문입니다. 회사에서는 보통 팀간에 지켜야 할 기본적인 코딩 표준이 존재하지만, 이런 코딩 표준이 있어도 다른 사람이 작성한 코드를 읽기는 매우 어렵습니다.

오픈소스 프로젝트는 많은 사람들이 참여하기 때문에 코딩 표준이 없으면 코드를 읽기 어렵습니다. 리눅스 커널 소스는 Documentation/CodingStyle에서 코딩 스타일 가이드를 제시하고 있으며, 오픈소스 C# IDE인 SharpDevelop의 경우 SharpDevelop Coding Style Guideline에서 코딩 표준을 제시합니다. 그러나, 이런 코딩 표준을 읽고 난 이후에도 오픈소스 코드나 다른 사람의 코드를 읽는 게 쉽지 않습니다. 왜? 개개인의 사고 방식이 다르고, 그것이 바로 코드에 드러나기 때문입니다. 그러나, 그 중에서 고칠 수 있는 몇 가지가 있는데, 바로 숨겨진 의도입니다. 예를 들어, 다음과 같은 코드는 매우 흔하게 등장합니다.
  if( state == 0 )
  {
     // do something
  }
  else if( state == 1 )
  {
    // do something
  }
이렇게 작성된 코드를 보면 코드의 의도가 무엇인지 전혀 알 수 없습니다. 그렇다고 "상태가 0인 경우"라고 주석을 다는 것은 전혀 의미가 없습니다. 그런 정도는 코드가 스스로 외치고 있습니다. 그렇다면, 어떤 주석을 달아야 할까요? "전송을 요청한 경우", "오류가 발생해서 재전송을 요청한 경우"와 같은 형태로 주석을 달아야 합니다. 주석을 달고 나면 코드를 읽는 것은 분명 쉽지만, 0과 1이라는 숫자는 찝찝합니다. 이처럼 어떤 의미를 갖는 숫자를 그대로 사용하는 것을 마법 상수(Magic Number)라 부릅니다. 따라서, 이를 의미있는 이름을 가진 상수로 변경하는 것이 바람직합니다.
  #define S_REQUEST_TRANS  0
  #define S_REQUEST_RETRY  1
상수 이름을 정의할 때는 해당 도메인에서만 쓰이는 것을 구분하기 위해 적절한 접두어를 사용하는 것이 좋습니다. 여기서는 "S_"라는 것을 붙였습니다. 리눅스 커널에서 네트워크 필터링 기능과 관련되어 정의된 상수들은 모두 "NF_"라는 접두어를 붙입니다. 이 때문에 코드 내의 어디에서든 NF_로 시작하면 넷필터라는 도메인에서 사용되는 상수라는 것을 알 수 있습니다. 상수를 정의하는 방법이나 의미의 사용은 사용하는 언어에 따라 조금씩 다릅니다. 클래스나 네임스페이스 안에 캡슐화하는 경우도 있을 것이고, 전역으로 선언하는 경우도 있을 겁니다. 보다 좋은 상수 선언 방법은 해당 언어 레퍼런스를 참고하기 바랍니다.
이제, 의미있는 상수를 정의했고, 0과 1을 대체하면 다음과 같이 됩니다.

  if( state == S_REQEUST_TRANS )
  {
     // do something
  }
  else if( state == S_REQUEST_RETRY )
  {
    // do something
  }
이런 코드에서는 코드 만으로도 의미가 명확하기 때문에 주석이 필요없습니다. 있던 주석을 제거해도 됩니다. 이처럼 숨겨진 의도를 갖고 있는 경우에는 적절하게 코드를 재정의하는 것으로 숨겨진 의도를 제거할 수 있습니다. 즉, 숨겨진 의도를 가진 코드를 주석으로 설명하는 것은 대부분의 경우에 잘못된 주석을 생성하는 것에 불과합니다. 그러나, 고수들의 코드이기 때문에 주석이 없다는 것은 잘못된 설명입니다. 주석을 써야 한다, 말아야 한다라는 이분법적인 사고가 "고수들의 코드엔 주석이 없다"라는 말을 만들어내는 것은 아닌가 합니다. 나는 고수들의 코드일수록 주석은 적재적소에 쓰일 수 있어야 한다고 생각합니다. 이런 경우를 하나 설명해 보겠습니다.

운영체제는 커널 공간과 사용자 공간으로 메모리를 나누어서 사용합니다. 메모리 공간의 정의는 해당 CPU 아키텍처의 스펙에 따르며, 비트 단위로 설정되기 때문에 각 비트 값의 의미를 알아야 합니다. 따라서, 해당 CPU의 매뉴얼을 보고 값을 코딩하는 것이 필요합니다. 리눅스 커널 소스 코드 역시 마찬가지로 CPU 매뉴얼을 보고 이 값을 코딩하고 있습니다. 리눅스 커널을 선택한 이유는 흔히, 고수들의 코드로 여겨지기 때문입니다. 다음은 커널 2.4에서 x86 시스템의 커널 공간과 사용자 공간을 나누기 위해 정의한 상수입니다. 다음 소스 코드는 include/asm-i386/segment.h 헤더 파일 전체입니다.
#ifndef _ASM_SEGMENT_H
#define _ASM_SEGMENT_H
#define __KERNEL_CS 0x10
#define __KERNEL_DS 0x18
#define __USER_CS 0x23
#define __USER_DS 0x2B
#endif
매우 단순하게 되어 있습니다. 0x10이나 0x18이라는 값 만으로는 어떤 특권 레벨을 선택한 것이며, 어떤 옵션들을 선택한 것인지 전혀 알 수 없습니다. 0x10이라는 값을 2진수로 바꾸고, 각 비트 순서에 대해서 CPU 매뉴얼을 보고 일일이 비교해야만 그 의미를 이해할 수 있습니다.

다음은 같은 소스 코드 파일을 커널 2.6에서 열어 본 것입니다. 단 4개의 상수만 정의되던 코드가 너무 길게 바뀌었는데, 그 중에 주석만 나타내겠습니다.
#ifndef _ASM_SEGMENT_H
#define _ASM_SEGMENT_H

/*
 * The layout of the per-CPU GDT under Linux:
 *
 *   0 - null
 *   1 - reserved
 *   2 - reserved
 *   3 - reserved
 *
 *   4 - unused     <==== new cacheline
 *   5 - unused
 *
 *  ------- start of TLS (Thread-Local Storage) segments:
 *
 *   6 - TLS segment #1     [ glibc"s TLS segment ]
 *   7 - TLS segment #2     [ Wine"s %fs Win32 segment ]
 *   8 - TLS segment #3
 *   9 - reserved
 *  10 - reserved
 *  11 - reserved
 *
 *  ------- start of kernel segments:
 *
 *  12 - kernel code segment    <==== new cacheline
 *  13 - kernel data segment
 *  14 - default user CS
 *  15 - default user DS
 *  16 - TSS
 *  17 - LDT
 *  18 - PNPBIOS support (16->32 gate)
 *  19 - PNPBIOS support
 *  20 - PNPBIOS support
 *  21 - PNPBIOS support
 *  22 - PNPBIOS support
 *  23 - APM BIOS support
 *  24 - APM BIOS support
 *  25 - APM BIOS support
 *
 *  26 - ESPFIX small SS
 *  27 - unused
 *  28 - unused
 *  29 - unused
 *  30 - unused
 *  31 - TSS for double fault handler
 */

#define GDT_ENTRY_TLS_ENTRIES 3
#define GDT_ENTRY_TLS_MIN 6
#define GDT_ENTRY_TLS_MAX   (GDT_ENTRY_TLS_MIN + GDT_ENTRY_TLS_ENTRIES - 1)

#define TLS_SIZE (GDT_ENTRY_TLS_ENTRIES * 8)
각 비트들에 대한 주석이 추가된 것을 알 수 있습니다. 또한, 각 비트 필드들의 의미도 세세하게 나뉘어서 GDT_ENTRY_TLS_ENTRIES와 같이 다양한 상수로 정의되어 있습니다. 실제 소스 코드는 상수가 더 많습니다. __USER_CS나 __KERNEL_CS 같은 정의도 다음과 같이 바뀌었습니다.
#define GDT_ENTRY_DEFAULT_USER_DS 15
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)

#define GDT_ENTRY_KERNEL_BASE 12

#define GDT_ENTRY_KERNEL_CS   (GDT_ENTRY_KERNEL_BASE + 0)
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
여전히 몇 개의 상수가 걸리지만, 보다 의미가 명확해진 것을 알 수 있습니다. 그렇지 않습니까?

나의 관점은 코드 속에 숨겨진 의도가 없어야 한다이며, 코드에서 제거할 수 있는 숨겨진 의도에 제한이 있다면 주석을 사용해서 명확하게 해야 한다는 것입니다. 또한, 커널 2.6의 소스 코드처럼 CPU 매뉴얼에서 볼 수 있는 배경 지식까지 포함되어 있어야 한다는 것입니다.

주석의 길이

다음으로 사람들이 얘기하는 것 중에 "주석의 길이"가 있습니다. 주석의 길이에 대한 논쟁이야 말로 쓸모없는 논쟁의 예라고 생각합니다. 주석은 필요한 만큼 길어지면 됩니다. 주석으로 소설을 써도 됩니다.

MS의 닷넷 프레임워크의 Hashtable 클래스의 구현의 첫 시작 부분의 주석을 보면 다음과 같습니다. 무려 139 라인의 긴 주석을 볼 수 있습니다. 어떤 알고리즘을 썼으며, 어떤 식으로 구현을 하고 있으며, 구현상의 논의점까지 설명하고 있으며, 참고자료까지 언급합니다. 자료구조 시간에 해시 테이블을 학습한 적이 있고, 소스 코드를 분석할 시간이 충분히 있다면 누구나 분석할 수 있을 정도로 잘 설명되어 있습니다.
// ==++==
// 
//   
//    Copyright (c) 2002 Microsoft Corporation.  All rights reserved.
//   
//    The use and distribution terms for this software are contained in the file
//    named license.txt, which can be found in the root of this distribution.
//    By using this software in any fashion, you are agreeing to be bound by the
//    terms of this license.
//   
//    You must not remove this notice, or any other, from this software.
//   
// 
// ==--==
/*============================================================
**
** Class:  Hashtable
**
**                                                                                      
**
** Purpose: Hash table implementation
**
** Date:  September 25, 1999
** 
===========================================================*/
namespace System.Collections {
  using System;
  using System.Runtime.Serialization;
  
  // The Hashtable class represents a dictionary of associated keys and values
  // with constant lookup time.
  // 
  // Objects used as keys in a hashtable must implement the GetHashCode
  // and Equals methods (or they can rely on the default implementations
  // inherited from Object if key equality is simply reference
  // equality). Furthermore, the GetHashCode and Equals methods of
  // a key object must produce the same results given the same parameters for the
  // entire time the key is present in the hashtable. In practical terms, this
  // means that key objects should be immutable, at least for the time they are
  // used as keys in a hashtable.
  // 
  // When entries are added to a hashtable, they are placed into
  // buckets based on the hashcode of their keys. Subsequent lookups of
  // keys will use the hashcode of the keys to only search a particular bucket,
  // thus substantially reducing the number of key comparisons required to find
  // an entry. A hashtable"s maximum load factor, which can be specified
  // when the hashtable is instantiated, determines the maximum ratio of
  // hashtable entries to hashtable buckets. Smaller load factors cause faster
  // average lookup times at the cost of increased memory consumption. The
  // default maximum load factor of 1.0 generally provides the best balance
  // between speed and size. As entries are added to a hashtable, the hashtable"s
  // actual load factor increases, and when the actual load factor reaches the
  // maximum load factor value, the number of buckets in the hashtable is
  // automatically increased by approximately a factor of two (to be precise, the
  // number of hashtable buckets is increased to the smallest prime number that
  // is larger than twice the current number of hashtable buckets).
  // 
  // Each object provides their own hash function, accessed by calling
  // GetHashCode().  However, one can write their own object 
  // implementing IHashCodeProvider and pass it to a constructor on
  // the Hashtable.  That hash function would be used for all objects in the table.
  // 
  // This Hashtable is implemented to support multiple concurrent readers and
  // one concurrent writer without using any synchronization primitives.  All
  // read methods essentially must protect themselves from a resize occuring while
  // they are running.  This was done by enforcing an ordering on inserts & 
  // removes, as well as removing some member variables and special casing the 
  // expand code to work in a temporary array instead of the live bucket array.  
  // All inserts must set a bucket"s value and key before setting the hash code 
  // & collision field.  All removes must clear the hash code & collision field,
  // then the key then value field (at least value should be set last).
  // 
  /// 
  [Serializable()] public class Hashtable : IDictionary, ISerializable, I
  DeserializationCallback, ICloneable
  {
    /*
      Implementation Notes:
      Dictionary was copied from Hashtable"s source - any bug fixes here 
      probably need to be made to Dictionary as well.
  
      This Hashtable uses double hashing.  There are hashsize buckets in the 
      table, and each bucket can contain 0 or 1 element.  We a bit to mark
      whether there"s been a collision when we inserted multiple elements
      (ie, an inserted item was hashed at least a second time and we probed 
      this bucket, but it was already in use).  Using the collision bit, we
      can terminate lookups & removes for elements that aren"t in the hash
      table more quickly.  We steal the most significant bit from the hash code
      to store the collision bit.
      Our hash function is of the following form:
  
      h(key, n) = h1(key) + n*h2(key)
  
      where n is the number of times we"ve hit a collided bucket and rehashed
      (on this particular lookup).  Here are our hash functions:
  
      h1(key) = GetHash(key);  // default implementation calls key.GetHashCode();
      h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1));
  
      The h1 can return any number.  h2 must return a number between 1 and
      hashsize - 1 that is relatively prime to hashsize (not a problem if 
      hashsize is prime).  (Knuth"s Art of Computer Programming, Vol. 3, p. 528-9)
      If this is true, then we are guaranteed to visit every bucket in exactly
      hashsize probes, since the least common multiple of hashsize and h2(key)
      will be hashsize * h2(key).  (This is the first number where adding h2 to
      h1 mod hashsize will be 0 and we will search the same bucket twice).
      
      We previously used a different h2(key, n) that was not constant.  That is a 
      horrifically bad idea, unless you can prove that series will never produce
      any identical numbers that overlap when you mod them by hashsize, for all
      subranges from i to i+hashsize, for all i.  It"s not worth investigating,
      since there was no clear benefit from using that hash function, and it was
      broken.
  
      For efficiency reasons, we"ve implemented this by storing h1 and h2 in a 
      temporary, and setting a variable called seed equal to h1.  We do a probe,
      and if we collided, we simply add h2 to seed each time through the loop.
  
      A good test for h2() is to subclass Hashtable, provide your own implementation
      of GetHash() that returns a constant, then add many items to the hash table.
      Make sure Count equals the number of items you inserted.
      Note that when we remove an item from the hash table, we set the key
      equal to buckets, if there was a collision in this bucket.  Otherwise
      we"d either wipe out the collision bit, or we"d still have an item in
      the hash table.
    */
  
    // Table of prime numbers to use as hash table sizes. Each entry is the
    // smallest prime number larger than twice the previous entry.
    private readonly static int[] primes = {
      11,17,23,29,37,47,59,71,89,107,131,163,197,239,293,353,431,521,631,761,919,
      1103,1327,1597,1931,2333,2801,3371,4049,4861,5839,7013,8419,10103,12143,14591,
      17519,21023,25229,30293,36353,43627,52361,62851,75431,90523, 108631, 130363, 
      156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403,
      968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 
      4999559, 5999471, 7199369 
    };
지금까지 살펴본 것처럼 주석에겐 아무 죄가 없다. 주석을 적재적소에 사용하지 못하는 것이 문제일 뿐이다. 주석과 코드를 함께 사용해서 읽을 수 있다면 설명 코드에 숨겨진 의도가 있더라도 "리팩터링"을 통해 수정하면 그만이다. 그러나, 주석도 없고, 숨겨진 의도나 지식으로 가득찬 코드는 읽기도 어렵고, "리팩터링"을 수행하기도 어렵다. 이런 코드가 진짜 문제일 뿐이다.

다음은 또 다른 주석의 형태이다. 나와 함께 다른 분이 작성한 코드다.
/*
  author: traxacun at ......
  created: 2002. 12.
  last modified: 2005. 11. 17.

  changed logs:
    20051117,mm_post_safe() modified,traxacun,HTTP Response Splitting 대응
    20051117,mm_get_safe() modified,traxacun,HTTP Response Splitting 대응
    20050803,mm_setTitle( $title )
    20040507,mm_hasSpace() added,traxacun
    20040507,mm_eatMarks() added,traxacun
    20040209,mm_getTeacherName() added,coffin
    20040204,mm_safe() added,traxacun
    20040131,mm_ip2long() added,traxacun
    20040131,mm_long2ip() added,traxacun
    20040128,mm_highlightWord() added,traxacun
    20040128,mm_clearXmlTag() added,traxacun
    20040112 mm_DropDownBoxEx() added, coffin
    20040103 mm_getSmallCover() added, coffin
    20031225,mm_isLogged() added,traxacun
    20031224,mm_displayError() added,traxacun
    20031215,mm_hasBannedWords() added,traxacun
    20031215,mm_isValidDigit() added,traxacun
    20031212,mm_isValidBase64() added,traxacun
    20031209,mm_isValidShortName() added,traxacun
    20031209,mm_isValidDisplayName() added,traxacun
    20031209,mm_isValidCategoryName() added,traxacun
    20031209,mm_isValidTemplateName() added,traxacun
    20031209,mm_isValidSkinName() added,traxacun
    20031209,mm_isBlank() added,traxacun
    20031204,mm_br2nl() added,traxacun
    20031204,mm_br2nl2() added,traxacun
    20031127,mm_wrapHtmlTagEx() added,traxacun
    20031127,mm_get_safe() added,traxacun
    20031126,mm_logError() added,traxacun
    20031126,mm_wrapHtmlTag() added,traxacun
    20031119,mm_post_safe() added, traxacun
    20031112,mm_getFlagValue() added,traxacun
    20031112,mm_getFlagName() added,traxacun
    20031121,mm_fieldError() added,coffin

  function:

  function string mm_displayList($array[][])
    interate through 2-dimensions array to display list of records.
    especially process the result from mm_getRecords().
    각 레코드 목록을 보여주는 함수
    mm_getRecords() 함수는 데이터베이스에서 쿼리한 결과를
    2차원 배열로 반환하며 이를 위 함수를 사용해서 쉽게 표시할 수 있다.

    ex. echo mm_displayList($arrList);
이 코드의 주석은 매우 길기 때문에 일부만 표시했습니다. 대부분의 웹 사이트 프로젝트들이 그렇듯이 시간이 없었습니다. 때문에, 라이브러리를 구축하더라도 별도의 함수 라이브러리를 위한 문서를 작성하는 것은 어려웠습니다. 또다른 문제는 이 라이브러리를 다른 사람들에게 전달하고, 사용하게 해야 한다는 것이었습니다. 일일이 교육하는 것으로 해결될 문제가 아니었습니다. 목표는 간단했습니다. 웹 사이트 프로젝트에 필요한 각 영역별로 하부 라이브러리를 개발하는 것, 정형화된 PHP 함수 호출 순서의 단순화, 또 이를 통한 PHP 함수 사용의 제거, 생산성 향상이었습니다. 때문에 위와 같은 주석 형태를 정의했습니다. 사람들은 함수 설명이 포함된 부분만을 편집해서 출력해서 자신의 책상에 올려두었고, 이는 훌륭한 함수 레퍼런스 역할을 했습니다. 문서화가 잘되어있기 때문에 개발자들의 저항은 적었습니다. 예를 들어, mm_queryValue( $query) 함수가 있습니다. 이 함수의 사용법은 다음과 같습니다.
  $query = "SELECT name from mm_users WHERE id = "myid"";
  $name = mm_queryValue( $query )
myid라는 ID를 가진 사용자가 있는지 알고 싶고, 그에 따라 처리하고 싶다면 다음과 같이 사용했습니다.
  $query = "SELECT name from mm_users WHERE id = "myid"";
  if( !mm_queryScalar( $query ) ) echo "그런 사용자 없음";
mm_queryValue() 같은 함수를 위해 대부분의 PHP 개발자들은 다음과 같은 코드를 사용해야 했습니다.
  $query = "SELECT name from mm_users WHERE id = "myid"";
  $dc = mysql_pconnect ($dbhost, $dbuser, $dbpassword);
  if( $dc == null ) echo "error";
  if( !mysql_select_db( $dbname, $dc ) ) echo "error";
  $rows = mysql_query($query, $dc);
  $row = mysql_fetch_array($rows);
  echo $row[0];  // 사용자 이름
  mysql_close( $dc );
라이브러리를 사용할 때와 사용하지 않을 때의 생산성 차이는 엄청났기 때문에 라이브러리를 사용했습니다. 그리고, 라이브러리를 사용하면서 코드들을 조합한 응용이 보일 때면 주석의 함수 예제에 지속적으로 추가하여 보완했습니다. 라이브러리 사용을 퍼트릴 수 있었던 데에는 주석의 역할이 컸습니다. XP도 아니고, 문서화도 하지 않지만, 적절한 주석 사용을 통해 코드가 변경되면 그 변경 내용이 즉각 반영되게 했습니다. 주석이 이처럼 긴 경우는 코드의 목적, 배경 지식을 설명하기 위한 경우가 대부분입니다. 실제 코드 내에서의 주석은 보통 이처럼 길지 않습니다. 매우 길어봐야 3줄 이내일 것이며, 3줄 이상이라면 이는 코드에 숨겨진 의도가 지나치게 많은 것이며, 이를 드러낼 수 있도록 코드를 바꿔야 하며, 주석을 줄일 수 있어야 합니다.

주석의 내용과 코드가 일치하지 않는 "거짓말하는 주석" 문제와 변경사항이 있을 때 마다 변경사항을 코드 내에 지속적으로 기록함으로써 버그가 자주 발생하는 지역을 찾아낼 수 있으며, 복잡한 요구 사항을 받을 때면 코드 보다 주석을 먼저 작성하고 코드를 작성하기를 활용해 볼 수 있습니다. 이에 대해서는 예전에 쓴 "프로그래밍 스타일"을 참고하기 바랍니다.
TAG :
댓글 입력
자료실

최근 본 상품0