Extremely Agile/TDD2008. 11. 4. 15:34
FindBugs라는 프로젝트가 있습니다. 코드의 정적 분석을 통해 코드에 내재된 버그를 찾는 솔루션을 만드는 프로젝트입니다.

많은 개발자들이 정적 분석을 통한 버그 탐색 방법을 도외시하는 경향이 있습니다만, 개발자들도 사람이니 '바보같은' 실수를 저지르게 되어 있기 마련이라는 점을 감안한다면, 짝 프로그래밍이나 코드 리뷰가 버그를 많이 줄여준다고 하더라도 그런 미련한 버그들이 코드에 뒤섞이는 것을 100% 방지할 수는 없습니다.

정적 분석 (static analysis) 방법이 그런 버그를 찾을 수 있게 도와준다면, 사용하지 않을 이유는 없어 보이는데요. 다행히 FindBugs 프로젝트는 Eclipse나 Netbeans 같은 IDE상에서도 사용할 수 있을 정도로 성숙되었고, 많은 분들이 쓰고 계십니다.



이 프로젝트 URL은 http://findbugs.sourceforge.net/ 입니다. 논문도 꽤 나온 것 같은데, 재미있어 보이네요. (아직 읽어보진 않았습니다.) Lesser GPL로 배포되고 있으니, 상업적 프로젝트에도 무리없이 적용 가능할 것 같습니다.

참고할만한 다른 링크들 :

http://benelog.egloos.com/2079841 - FindBugs + Maven 2 + Hudson
http://findbugs.blogspot.com/ - FindBugs 공식 블로그
http://findbugs.sourceforge.net/bugDescriptions.html - 버그 패턴 설명
http://www.ibm.com/developerworks/kr/library/tutorial/j-cq11207/index.html - 지속적 통합과 결함 발견
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 동영상 내용 많이 도움이 될 것 같네요. 제 블로그의 포스트에서도 이 포스트에 링크를 걸었습니다~ 잘 봤습니다.

    2009.01.24 07:09 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/TDD2008. 11. 4. 14:20
앞서 남이 짠 클래스 코드를 어떻게 리팩토링하면 jMock을 통해 테스트하기가 좀 더 쉬워지는지를 살펴봤습니다. 그런데 현실 세계의 클래스들이 전부 그렇게 간단하게 만들어져 있으면 문제가 쉬운데, 실제로 테스트를 진행하다보면 그렇지 않은 경우를 종종 만나게 됩니다.

가장 골치아픈 경우 중 하나는, 생성자 안에 해당 클래스의 비즈니스 로직(?)이 들어가 있는 경우죠. 그러니까 좀 돌려서 말하면, 클래스의 생성자 코드 안에 테스트해야하는 알고리즘의 일부가 들어가 있는 경우입니다. 이 알고리즘이 대상으로 하는 객체들 중 하나를 Mock 객체로 만들어 테스트해야 한다면, 상황은 간단히 풀리지 않습니다. 다음 코드를 보시죠.

class Foo {
    public Foo() {
        ...
        bar.doSomething();
        ...
    }
    ...
};

bar에게 뭔가를 하고 있습니다. 그런데 bar에 doSomething을 호출하는 순간 네트워크 접속이 발생하기 때문에, 그 상황을 피하기 위해 bar를 mock 객체로 만들고 싶다고 해 보죠. 앞선 글에서 사용한 방법(아마 패턴을 아시는 분이라면 그 방법이 소위 '템플릿 메소드 패턴'인건 아시겠습니다만...)을 그대로 써먹자면 다음과 같이 해야 할 텐데요.

class Foo {
    protected void doSomething() {
        ..
        bar.doSomething();
        ..
    }

    public Foo() {
        ...
        doSomething();
        ...
    }
    ...
}

얼핏 보면 그럴듯해 보입니다만, 다음과 같이 하는 순간 문제가 복잡해집니다.

class FooT extends Foo {

    private Bar bar;

    protected void doSomething() {
        ...
        // do something with bar
    }

    public FooT() {
        super();
        // do something with bar
    }
}

왜 문제가 복잡해지죠? FooT의 생성자가 불리는 순간, 상위 클래스의 생성자가 불리면서 doSomething()이 호출되는데, 이 때 호출되는 doSomething은 하위 클래스에 정의된 doSomething이어야 하거든요. 그런데 아직 객체가 완전히 초기화도 되지 않은 상태이니, 하위 클래스의 doSomething이 제대로 수행될 리가 없죠. 이런 문제는 C++이건 Java건 공통적으로 발생하는 문제입니다. C++에서는 가상 함수 포인터 초기화에 관련된 오류가 발생하면서 프로그램이 죽는 현상이 발생하고, Java에서는 멤버 변수 초기화에 관한 오류 메시지가 뜨면서 프로그램이 죽습니다.

이 문제는 "Don't call subclass methods from a superclass constructor"라는 글에 잘 정리가 되어 있습니다. 요지는 When designing a class for subclassing, it’s important to avoid calling any method that the subclass could or must override from the superclass constructor. 인데, 간단히 요약하면 "하위 클래스가 오버라이드할 가능성이 있는 메소드를 생성자 안에서 호출하는 것은 피해라"라는 뜻입니다. 하위 클래스에서 오버라이드를 해버리면, 이런 문제가 생길 수 있으니까요.

그러니까 생성자 안에 테스트 해야할 알고리즘의 일부가 들어있는 경우에는, 원래 클래스의 코드를 수정하는 일이 불가피해지고 맙니다. 그런 일이 자주 생기면 좋지 않기 때문에, 가급적 생성자 안에는 "초기화에 관련된 코드들"만을 두는 것이 바람직하겠습니다.

그럼 "어쩔수 없이 원래 클래스 코드를 수정해야 하는 경우"에는 어떻게 수정하는 것이 바람직 할까요? 가령 다음과 같은 클래스가 있다고 해 봅시다. 제가 실무에서 만난 클래스입니다.

public class Connection implements Definition {
   ...
   public Connection(String ip, int port, int type) throws Exception {
      ...
   }
   ...
}

이 생성자는 내부적으로 Socket 객체를 생성하여 그 메소드를 호출합니다. 골치아픈 생성자죠. ㅋㅋ 우리가 해결해야 할 목표는, Socket 객체를mock객체로 바꿔칠 수 있는 방법을 Connection 클래스에 추가하는 것이었습니다. 그래서 코드를 다음과 같이 변경했습니다.

public class Connection implements Definition {
   ...
   public Connection(String ip, int port, int type) throws Exception {
      init(ip, port, type, null);
   }
   
  Connection(String ip, int port, int type, Socket alternativeSocket) throws Exception {
      init(ip, port, type, alternativeSocket);
   }

   private void init( ... ) {
      // if alternativeSocket is not null, do something with it.
      // or, create Socket object and deal with it
   }
   ...
}

간단히 요약하면, (1) initialization 로직은 별도의 private 함수로 refactoring하고 (하위 클래스에 의해 오버라이드 되지 않아야 하기 때문에 private입니다) 그 함수의 네번째 인자로 외부에서 정의한 소켓 객체 (mock 객체겠죠)를 받은 경우 그 객체를 가지고 필요한 작업을 하도록 변경했으며, (2) 테스트를 위한 별도의 생성자를 정의하고, 그 생성자의 네번째 인자로 mock 객체를 인자로 받아 init 함수에 넘기도록 한 다음 (3) 원래 생성자는 init 함수를 호출하되 네번째 인자로 null을 넘기도록 코드를 변경했습니다.

이렇게 함으로써 코드에 그다지 '큰' 변경을 가하지 않고서도 테스트 가능성을 확보할 수 있었습니다. 새로 추가된 생성자는 그 scope가 package 이어야 합니다. 그래야 테스트 코드 안에서만 사용이 가능할테니까요.

[다음 글에서 계속...]

Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2008. 11. 3. 16:09

그러면 오늘은 jMock을 이용해 남의 코드를 테스팅하는 과정을 한번 살펴보겠습니다. 정답이라고 할 수는 없고, 저 개인적인 경험에 따른 것이니, 참고 정도 하시면 좋을 것 같습니다.

남의 코드를 테스팅할 때 중점적으로 고려한 사항은 다음과 같습니다.

1. 남의 코드의 인터페이스는 손대지 않는다.
2. 남의 코드의 주 알고리즘은 손대지 않는다.


물론 2는 어쩔 수 없이 손대야 할 경우도 생기긴 합니다만, 가급적 그러지 않는 것이 정신건강상 이롭습니다. 왜 그런지는 아마 여러분들도 잘 아실 거라 생각합니다.

그러면 이제 실제 사례를 살펴보겠습니다. ConnectionManager라는 클래스입니다. 이 클래스는 대략 다음과 같은 골격을 가지고 있습니다.

public class ConnectionManager extends Thread implements Definition {
  ...
 
  ConnectionManager(PolicyAgent pa) {
  ...
  }
  ...

  public void connect() {
    Connection conn = null;
    // main algorithm
    ...
    conn = new Connection(this.policyAgent.primaryServer, this.svrPort, ...);
    ...
  }

  ...
}

이 코드를 테스트하는 데 있어서 가장 큰 문제는, 내부에서 생성하고 관리하는 Connection 객체가 어딘가로 TCP/IP 연결을 시도한다는 점이었습니다. 코드를 단위테스트하는 와중에는 해당 서버가 없었기 때문에, 우리는 Connection 객체를 어떻게든 'mock' 해야만 했습니다. 그런데 생성하는 코드가 주 알고리즘에 뒤섞여있었기 때문에 애매한 상태였죠.

그래서, 우선은 주 알고리즘에서 Connection 객체를 생성하는 코드를 별도의 메소드로 분리하고, 알고리즘 안에 포함되어 있던 new Connection 부분을 전부 createConnection을 호출하도록 변경했습니다.

public class ConnectionManager extends Thread implements Definition {
  ...
 
  ConnectionManager(PolicyAgent pa) {
  ...
  }
  ...

  protected Connection createConnection(String svr, int port, int type) {
    return new Connection(svr, port, type);
  }

  public void connect() {
    Connection conn = null;
    // main algorithm
    ...
    conn = createConnection(this.policyAgent.primaryServer, this.svrPort, ...);
    ...
  }

  ...
}

이렇게 한 다음에, JUnit 코드 안에서 ConnectionManager 클래스 코드를 계승하여 ConnectionManagerT라는 클래스를 정의했습니다.

class ConnectionManagerT extends ConnectionManager {
 private List<Connection> extConns = null;

 public ConnectionManagerT(PolicyAgent parent) {
  super(parent);
  this.extConns = new LinkedList<Connection>();
 }

 void addExternalConnection(Connection extConn) {
  synchronized( this.extConns ) {
   this.extConns.add( extConn );
  }
 }

 private Connection getExternalConnection() {
  synchronized( this.extConns ) {
   if ( this.extConns.isEmpty() ) return null;
   return this.extConns.remove(0);
  }
 }

 protected Connection createConnection(String svr, int port, int type) {
  Connection conn = null;
  if ( (conn = getExternalConnection()) == null ) {
   return super.createConnection(svr, port, type);
  }
  return conn;
 }
 ...
}

그리고 이 클래스 안에서 createConnection메소드를 오버라이드 했습니다. 이제 테스트 코드에서는 ConnectionManager 클래스를 사용하는 대신, ConnectionManagerT 클래스를 사용해서 테스트를 진행하면 됩니다. ConnectionManager 클래스에 정의된 알고리즘이 createConnection을 호출하는 순간, 하위 클래스에 정의된 createConnection이 호출될 것이고, 이 메소드는 new Connection을 하는 대신 getExternalConnection 메소드를 호출하여 connection을 얻을 것이기 때문에, mock 객체를 통해 주 알고리즘의 테스트가 진행되도록 만들 수 있습니다. setExternalConnection 메소드를 사용해 mock 객체를 ConnectionManagenrT 객체에 세팅해두면 되는 것이죠.

그러니까 테스트 케이스 코드 안에서는 다음과 같은 짓을 순차적으로 하면 된다는 겁니다.

  PolicyAgent pa = new PolicyAgent();
  pa.initiate();

  ConnectionManagerT cm = new ConnectionManagerT(pa);

  ...

  final Connection connMock1 = context.mock( Connection.class );
  cm.addExternalConnection( connMock1 );

  //
  // test no-signature error scenario
  //

  context.checking( new Expectations() {{
   ...
  }});

  cm.run(); // 알고리즘 테스트

  ... // 수많은 assertion 코드들...

어떻습니까? 이렇게 하면 남의 코드를 몇줄 고치지 않고서도 테스트를 진행할 수 있습니다. new Connection 때리는 부분을 별도의 overridable 메소드로 분리하는 것 만으로, 테스트 가능성을 높인 코드를 만들 수 있다는 이야깁니다.

이런 가능성은 TDD 형태로 테스트 코드를 먼저 작성하건, 아니면 작성된 코드에 테스트 코드를 추가하건, 순서 불문하고 테스트 코드를 작성하느라 고민하지 않으면 좀처럼 열리지 않습니다. 의외로 테스트 가능한 코드를 만드는 것은 아주 간단한 리팩토링(Refactoring)으로부터 시작되는데도 말이죠.

Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2008. 10. 31. 14:31

jMock은 Mock 객체를 이용해 테스트를 지원하는 라이브러리입니다. www.jMock.org에서 다운받으실 수 있습니다. 요즘 회사에서 '다른 사람이 작성한 코드'를 좀 더 '테스트 가능'(testable)하게 만드는 동시에 test coverage를 높이는 일을 하고 있습니다. 작년까지 했던 일과는 조금 다른 일이라, 재미있습니다.

jMock 계열의 라이브러리로는 EasyMock 같은 것도 있습니다만, 써보니까 'Easy'하다고 꼭 좋은것만도 아니어서 다시 jMock으로 돌아왔습니다. 돌아와보니 jMock이라고 또 그리 사용법이 복잡하지는 않습니다. 사용법이 좀 까다로와 보여서 진입장벽이 '약간' 있을 뿐이죠.

Mock 라이브러리를 사용해 테스트 코드를 작성하는 목적은, '테스트 대상 알고리즘을 그 알고리즘이 대상으로 하는 자원과 분리'하는 것이라고 볼 수 있겠습니다. 가령 알고리즘 foo가 어떤 자원 bar들을 대상으로 돌아간다고 해 봅시다. bar의 구현 상태와는 독립적으로, foo의 정확성만을 테스트하고 싶다고 해 봅시다. 그 경우에는 '원하는 대로 동작하는 것이 보장된' 가짜 bar 객체들만을 만들어 두고 그 위에서 foo를 돌리게 되면 foo의 정확성을 무리없이 검증할 수 있을 것입니다.

그러니 결국 Mock 라이브러리들이 하는 일은, '분리(separation)'라고 요약할 수 있겠습니다.

실제 '남의 코드'를 테스팅하는 사례를 살펴보기전에, 우선 jMock 라이브러리 사용법의 '골격'부터 알아보도록 하겠습니다. jMock라이브러리를 사용하기 위해서는, 대략 다음과 같은 클래스들을 임포트해 둘 필요가 있습니다. (뭐 eclipse가 알아서 다 해주긴 하지만..ㅎㅎ) 전 JUnit 4를 가지고 프로그래밍을 했기 때문에, JUnit4Mockery 클래스를 임포트하도록 했습니다.

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;

그런 다음 이제 JUnit 테스트 클래스의 안을 채워나가야 하는데요. ConnectionManager라는 클래스가 있다고 치고, 그 클래스를 테스트하는 JUnit4 클래스를 구현한다고 가정하겠습니다. 그 클래스 제일 윗부분에 다음과 같은 부분을 둡니다.

public class ConnectionManagerTest {

  private Mockery context = new JUnit4Mockery() {{
    // enable mocking of class also (not only interface!)
    setImposteriser(ClassImposteriser.INSTANCE);
  }};
  ...

}

보통 Mock 라이브러리들은 Interface 클래스를 인자로 받아, 그 인터페이스가 하는 일을 흉내내는 가짜 객체를 생성합니다. 그런데 EasyMock이나 JMock은 확장을 통해 일반 클래스를 인자로 받아서 Mock 객체를 생성할 수 있도록 배려하고 있습니다. 위의 적색으로 표시된 부분은, 일반 클래스를 인자로 받아 Mock 객체를 생성할 수 있도록 지시하는 부분이라고 생각하시면 됩니다.

이렇게 하고 나면 이제 테스트 코드를 작성하면 되는데요. 대략 다음과 같은 순서를 따릅니다.

 @Test
 public void testRun() throws Exception {

  // 1. Mock 객체 생성
  final Connection mock = context.mock( Connection.class );

  // 2. Mock 객체를 테스트 대상 알고리즘에 세팅
  ...
 
  // 3. 알고리즘을 돌렸을 때 mock 객체에 무슨 일이 일어나게 되는지를 명시
  context.checking( new Expectations() {{
   allowing (mock).getInteger(); will(returnValue(34));
   oneOf (mock).writeInteger(56);
   allowing (mock).close();

  }});

  // 4. 알고리즘 구동
  ...

  // 5. 각종 assert 문들 삽입
  ...
 }

우선, Mock 객체를 만듭니다. context.mock의 인자로 객체를 찍어낼 클래스를 인자로 넘겨주어야합니다. 그런 다음, 만들어진 mock 객체를 테스트 대상 알고리즘에 세팅합니다. 알고리즘을 구동할 때 mock 객체를 인자로 넘겨주게 되어 있다면, 이 단계를 생략해도 무방하겠습니다.

그런 다음, 알고리즘을 실제로 돌리면 mock 객체에 어떤 행위가 일어나게 되리라 '기대'(expectations)하는지를 세팅합니다. context.checking 메소드에 Expectations객체를 만들어 넘겨주는 식으로 세팅하게 되는데, 그 안에 발생할 것으로 기대되는 행위 목록을 적어주면 됩니다. 일단 위의 예제의 의미를 간단히 설명드리면,

allowing... 으로 시작되는 줄은, 알고리즘 구동 과정에서 mock 객체의 getInteger() 함수가 여러번 불리게 될 수 있는데, 그 때 마다 34를 리턴하도록(will(returnValue(34))) 지시하고 있습니다.
oneOf... 로 시작되는 줄은, 알고리즘 구동 과정에서 mock 객체의 writeInteger 함수가 단 한번 불리게 되는데, 그 때 인자로 56이 전달되어 호출되어야 함을 명시하고 있습니다.
마지막의 allowing...으로 시작되는 줄은, 알고리즘 구동 과정에서 mock 객체의 close() 함수가 여러번 불리게 될 수 있다는 것을 명시하고 있습니다.

결국, mock 객체가 알고리즘의 요구에 어떻게 반응하여야 하는지를 명시하는 셈입니다. 이 부분을 잘 작성해주면 mock 객체에 전달되는 값이 예상치와 일치하는 지를 검사하여 알고리즘이 제대로 된 값을 사용해 mock 객체와 통신하는지를 검사할 수 있게 되고, 결국 알고리즘의 정확성을 검증할 수 있게 됩니다.

이렇게 expectation의 목록을 작성하고 나면, 그 다음으로는 알고리즘을 구동시키고, assert구문들을 사용하여 (assertTrue, assertFalse 등) 구동 후의 상태가 예상한 것과 일치하는지를 보면 됩니다.

expection을 작성하는 문법이나, JMock 사용법에 대해 더 자세히 알고싶으신 분은 JMock 웹사이트에 올라온 cheat sheet를 보시면 좋을 것 같습니다.

[다음 글에 이어서...]

Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 이상곤

    JMock에 대한 강좌 잘 읽었습니다. 헌데 따라 하는 중 ClassImposteriser 클래스를 임포트하는 과정에서 바로 막혔습니다. ㅠㅠ package가 org.jmock.lib.legacy 아래에 ClassImposteriser 있다고 나와 있고 API 문서를 봐도 그렇게 나와 있는데 stable 버전 2.5.1 jar 파일에는 그 이하에 legacy가 안보입니다. 혹시 제가 뭔가 간과 한 부분이 있는지요?

    2010.01.20 14:26 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/TDD2008. 10. 21. 13:53
TDD를 배우고 실무에 적용해보고 나면, 프로그래머는 대략 다음 두 가지 중 하나의 유형으로 바뀝니다.

1. TDD를 굉장히 열정적으로 사용하는 프로그래머
2. 테스트의 필요성을 절감하고 테스트 케이스를 작성하긴 하는데, 그렇게 TDD를 열심히 하지는 않는 프로그래머

저는 이 중 어느쪽이냐 하면, 후자 쪽입니다. 아직 TDD에 확실히 익숙해지지 않아서 그럴 겁니다. 물론, 자기가 작성하는 코드에 대한 자신감이 높은 프로그래머도 두번째 유형에 속할 가능성이 높습니다. 이런 프로그래머는 TDD는 너무 오버헤드가 높은 방법이라고 느끼는 경향이 있습니다.

사용자 삽입 이미지

http://www.nilkanth.com/archives/2007/06/08/three-monkeys-of-test-driven-development/


그런데 두 번째 유형의 프로그래머들이 흔히 빠지는 함정이 하나 있습니다. 블랙박스 테스트(blackbox test)를 하게 될 가능성이죠.

보통 프로그램을 클래스 단위로 구분해서 작성하게 되면, 최소한 클래스가 하나 만들어 질 때 마다 테스트 케이스들을 만들도록 하는 것이 좋습니다. 그런데 프로그래머에 따라서는 클래스가 아니라, 모듈이 하나씩 만들어 질 때마다 테스트 케이스들을 작성하기도 합니다.

그렇게 하면 보통 모듈에 속한 모든 클래스들에 대해 테스트 케이스를 작성하기는 어려워집니다. 정말로 어려워서 어렵다는 것이 아니라 귀찮아서 생략할 가능성이 높아지기 때문에 어렵다고 하는 것이죠. 그렇게 되면 모듈의 관리자 클래스(Manager class)에 대한 테스트 케이스들만 만들어지게 됩니다. 모듈의 인터페이스에 대한 테스트 케이스만 만들어지게 된다는 것이죠.

결국 모듈이 블랙 박스가 됩니다.

블랙 박스 테스트가 의미가 없다는 뜻은 아닙니다. 하지만 추상화 단계의 위쪽으로 올라가 테스트를 하게 되면, 거기서 처리해야 할 테스트의 가짓수가 늘어나게 됩니다. 그 모든 경우를 다 따져 테스트 케이스를 작성하면 좋겠지만, 그렇게 못할 가능성이 높습니다.

결정적으로, 블랙 박스 테스트로는 "모듈의 어느 부분에서" 오류가 발생했는지를 감지하기가 어렵다는 문제도 있습니다. 그렇기 때문에 가급적 코드의 가장 작은 단위에 대해서부터 단위 테스트를 진행하는 것이 좋다는 것이죠.

TDD를 하지 않더라도 그렇게 하면 적어도 버그의 수를 줄이는 데는 도움이 되는 것을 경험했습니다. 거기다 부수적인 이득도 얻을 수 있는데, "테스트가 쉬운 코드를 만든다"는 목적은 그대로 유지할 수 있다는 겁니다. 물론 "테스트 코드를 통해 실제 코드의 디자인이 결정되는" 신기한 경험은 할 수 없겠지만 말이죠.
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008. 10. 20. 20:45

어제부터 탐험적 (탐색적) 테스팅 강의를 듣고 있습니다. 강사는 제임스 바흐(James Bach)이고, 여기서 김창준님을 처음 뵈었습니다. 이 강의를 앞으로 이틀은 더 들어야 합니다만, 개발자로서 첫 이틀의 강의를 들은 소감을 간단히 정리하자면, 테스트를 대하는 제 시각이 조금은 넓어졌다는 것을 들 수 있겠습니다.

흔히 개발자는 테스트를 jUnit이나 CppUnit으로 정의된 테스트 케이스(Test Case) 묶음 정도로 생각하는 경향이 있습니다. 물론 이 정도로만 생각하더라도 많은 효과가 있는 것은 사실입니다. TDD와 연계해서 단위 테스트 작업을 하게 되면 효과는 좀 더 높아지게 되기도 하죠. 테스트가 쉬운(Testable) 코드를 만들게 된다는 부수적인 효과도 있구요. 제임스 바흐도 개발자가 해야 할 일 중 하나로 이것을 지적했습니다.

하지만 일반적으로 테스트라고 불리는 작업은 그 규모가 엄청나게 큰 작업입니다. 무엇을 테스트 했는지, 테스트를 했다면 얼마나 했는지, 했다면 제대로 하기나 한 것인지를 추적하는 작업만 해도 만만하지가 않습니다. 테스트 케이스 중심적인 테스트 방법, 스크립트 중심적인 테스트 방법이 각광받기 시작한 것은 그래서 인것 같습니다. 하지만 개발 계획이 컨베이어 벨트가 아니듯, 개발자나 테스터 또한 그 컨베이어 벨트 주위에 공구를 들고 늘어서있는 사람들이 될 수는 없습니다. 잘 정의된 계획이나 테스트 케이스, 또는 스크립트들은 종종 그 사실을 잊습니다. 테스트를 수행하는 사람들의 창의적이고 탐험적인 측면을 충분히 고려하지 않는 것이죠. 그래서 탐험적 테스트라는 접근법이 등장했다고 생각합니다.

탐험적 테스트 접근법은 테스터가 시스템에 몰입하여 그 시스템에 숨겨진 결함을 찾아내는 과정에 주목하고, 어떻게 하면 그 결함을 보다 빨리, 보다 많이 찾아낼 수 있는지를 알려주려고 애씁니다. 아직 실전에 적용해 본 바가 없어서 '정말 그런지' 보증할 수는 없습니다만, 어쨌던 테스팅이라는 작업에 문외한인 저에게도 그럴싸 해 보이는 접근법인것 같기는 합니다.

강의 중간 중간에 다양한 연습문제들이 등장하는데, 이 연습문제들을 풀어보는 것 만으로도 얻을 수 있는 것이 제법 되는 것 같습니다. 특히 훌륭한 테스터가 되려면 이런 것들을 할 줄 알아야 되겠구나, 하고 생각한 것이 두 가지 정도 있는데, 첫 번째는 '현상'을 '징후'로 해석하는 능력이고, 두 번째는 끝까지 다양한 시도를 해 보는 끈기가 되겠습니다. 부연설명을 좀 하자면...

1.
저는 오늘 연습문제를 풀면서 신기한 경험을 했습니다. '뭔가 이상하구나(weird)'하고 느꼈지만 '설마 이게 버그겠어?'하고 체크리스트에 적어두지 않고 넘겨버린 것들(테스트 케이스를 그럴듯하게 만들기 위해 도외시했던 문제들)이 '발견된 버그들' 목록에 죄다 등장하는 겁니다. 폰트 색깔, 프로세스가 죽을때 덩달아 죽는 프로세스의 존재 같은 것들이 바로 그것이었습니다. 이런 것들은 제 입장에서는 그저 '현상'이지 버그의 '징후'는 아니었는데, 테스터 분들은 그것들을 전부 '버그의 징후'로 잡아 내더군요. 이런 것들은 반복적인 테스트 훈련이 없이는 체득될 수 없는 경험이 아닐까, 그런 생각을 했습니다.

2.
오늘 김창준님이 연습문제 풀이에 직접 참여하셨는데요. 저로서는 'QA 만족 조건을 알려주지 않으면 테스트를 진행할 수 없다. 테스트를 진행하지 않는다고 나를 해고한다면, 나는 당신을 고소할 생각이다'라고 으름장을 놓는 방법밖에는 생각이 나질 않았는데 ㅋㅋ 김창준님은 그런 상황에서도 평정심을 잃지 않고 이런 저런 시도를 해 보시더군요. 물론 실제 상황은 아니니까 그런 시도가 가능한 측면도 있습니다만, 생각을 펼쳐 나가는 방법이 보통 주변에서 제가 만날 수 있는 사람하고는 조금 달라서 신기했고, 그 끈기가 좀 놀라왔습니다. 결국 테스터가 가져야 할 가장 중요한 덕목 중 하나는 바로 끈기가 아닐까, 그런 생각이 들더군요. 생각의 패턴은 훈련을 거듭하면 개선될 수 있는 부분이라고 생각됐구요.

아무튼 강의를 들으면서 이런 저런 생각들을 좀 했는데, 나중에 기회가 있으면 한번 정리해 보도록 하겠습니다. 혹시 이 블로그에 들르시는 분 중에 저랑 같이 이 강의를 들으시는 분 계신가요? 계시면 나중에 인사라도 했으면 좋겠습니다. 제 이름은 이병준이고, 좌측 열 앞에서 세번째쯤 앉아 있습니다. :-)

Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008. 9. 24. 11:49

불확실성(Uncertainty) : 장래 일어날 수 있는 사상()에 관해서 인간이 가진 정보의 정확성에 대한 하나의 구분. (출처 : 네이버 백과사전)

네이버 백과사전에 나온 불확실성의 정의에 따르면, 의사결정자가 가지고 있는 정보의 정확성은 ㉠ 확실성, ㉡ 리스크(risk), ㉢ 불확실성, ㉣ 무지()의 4종류로 분류할 수 있다고 합니다. 관련 부분을 인용해보죠.

㉠의 확실성은 무엇이 일어날지 확정적으로 알고 있는 경우를 말한다. ㉡의 리스크는 무엇이 일어날지 확정적으로는 알 수 없으나, 일어날 수 있는 상태는 알고 있고, 또 그 확률분포()도 알고 있는 경우를 말한다. 이에 대하여 ㉢의 불확실성은 일어날 수 있는 상태는 알고 있으나, 그 확률분포를 알지 못하는 경우를 말한다. ㉣의 무지란 무엇이 일어날지, 어떠한 상태가 일어날지, 전혀 예견할 수 없는 경우를 말한다. 한편, 넓은 뜻의 불확실성이란 ㉡의 리스크와 ㉢의 불확실성의 양자를 가리킨다.

이런 개념을 최초로 정립한 사람들은 경제학자들입니다. 돈이 걸리면 어떻게든 위험성을 줄이는 것이 좋으니까, 당연하겠죠. 소프트웨어 프로젝트에도 돈이 걸려 있기 때문에, 무슨 방법을 쓰던 위험성을 줄이면 줄일수록 이득입니다.

하지만 '확실성'이 갖는 정의 - 무엇이 일어날 지 확정적으로 알고 있는 경우 - 에 비추어보면, 확실성이란 달성이 불가능한 꿈과 같습니다. 확실성을 성취하려면 타임머신을 발명하던가 해야 합니다. '아무 일도 하지 않으면 되지 않느냐?'고 물으실 분도 계실 것 같은데, 아무 일도 하지 않으면 그 순간부터 유보수익이 사라지고, 기회비용 문제가 발생하기 시작합니다.

하지만 '불확실성'에 대한 '확실한 사실'이 한가지 있긴 합니다. 우리가 알고자 하는 것이 미래의 특정 시점에 일어날 사건에 대한 불확실성의 정도라고  한다면, 이 불확실성은 그 시점에 가까이 가면 갈수록 감소하는 경향을 보인다는 것이죠. 가령 우리 아기가 몇개월때 첫 걸음마를 뗄 것인지를 알고 싶다고 해 봅시다. 그리고 18개월쯤에는 첫걸음마를 뗄 것으로 예상한다고 해 봅시다. 이 예상이 갖는 불확실성은 시간이 흘러 18개월 근처로 가면 갈수록 감소합니다. 17개월이 되었는데도 엎드려 고개를 드는 것이 고작이라고 한다면? 아마 18개월에 첫 걸음마를 떼기는 힘들어지겠죠. 예상이 맞건 틀리건, 불확실성은 이런 식으로 감소 추세를 보이기 시작합니다. (물론 아이가 예상하지 못한 병에 걸린다거나 하는 불행한 일이 생기면 불확실성이 급상승하는 일도 생길 수 있긴 하겠습니다.)

불확실성이 갖는 이러한 속성과, 확실성, 위험, 지식이 갖는 속성들을 종합해 보면, 우리는 분명해 보이는 몇 가지 결론에 이르게 됩니다.

1. 시간이 흐르면 불확실성은 차츰 감소한다.
2. 지식이 늘어나면 불확실성은 차츰 감소한다.

이 지식에는 '확률적 지식'도 포함됩니다. 시간이 흐르면 사람들은 '어떤 사실'의 '발생 가능성'에 대한 지식을 자연스럽게 취득하게 됩니다. 확률적 지식이 100% 정확하다고는 볼 수 없습니다만, 적어도 한 잣대는 되어줄 수 있기 때문에, 불확실성은 그에 따라 차츰 감소하게 됩니다.

이런 불확실성의 개념이 소프트웨어 개발 프로젝트 관리 방법론의 한 근간으로 등장한 것은 사실 오래된 일입니다. 1981년 베리 뵘은 이후 스티브 멕코넬이 '불확실성 원추(Cone of Uncertainty)'라고 부르게 되는 그래프를 하나 그렸는데, 이것이 시발이라고 봐도 무방할 것 같습니다. 이 그래프가 보여주는 것은 사실 위에 제시했던 두 가지 뻔한 결론에 다름 아닙니다. 하지만 전통적인 개발 방법론이 이 자명한 사실을 인정하게 되는 데는 약간의 시간이 필요했죠. 애자일 선언문이 발표된 것이 2001년도이니까, 그 사람들이 지금은 '애자일'이라고 불리는 방법론을 가다듬는 데 필요했을 10년 정도의 세월을 감안하면, 불확실성이 화두로 등장하는 데는 10년 정도의 시간이 필요했다고 봐도 될 것 같네요.

그럼  불확실성이 프로젝트 관리에 있어서 가장 중요한 문제중 하나로 등장한 이유는 뭘까요? 아마 다 아시겠지만, 그것은 초기 계획대로 굴러가는 프로젝트가 별로 없기 때문이며, 초기 설계대로 우아하게 마무리되는 프로그램이 별로 없기 때문이고, 그런 일이 빈번하게 생기면 프로젝트 팀원들의 '꼭지가 돌아버리기' 때문입니다. 그리고 그런 프로젝트는 고객에게 '최선의 가치를 전달'하기가 점점 더 어려워지죠. 전통적인 방법론들은 프로젝트 도중에도 불쑥 뿔쑥 등장하곤 하는 이런 '꼭지도는 일들'을 어떻게 처리해야 하는지에 대해서는 별 이야기가 없습니다.

이쯤에서 이미 식상해졌을 지도 모를 애자일 이야기를 조금만 더 하자면... 애자일 방법론은 사실 불확실성에 관한 방법론입니다. 흔한 오해중에 한가지는 '애자일 방법론은 고객 중심 방법론이다'라는 것인데, 사실 이 이야기는 반 정도는 맞고 반 정도는 틀립니다. 사용자를 무조건 만족시키는 것이 애자일 방법론의 목표는 아니기 때문이죠. 고객이 중요한 것은, 사용자가 프로젝트의 성패에 관련된 가장 중요한 이해 당사자 중 하나이기 때문입니다. 고객을 중요하게 생각하는 것은, 그들과 어떻게 의사소통하느냐에 따라 프로젝트의 불확실성 정도가 굉장히 많이 달라질 수 있기 때문입니다.

흔히 보는 장면
A : 이번 프로젝트의 요구사항 명세표를 분석해야 하는데요.
B : 누가 작성한 명세표죠?
C : 영업팀에서 작성한 겁니다.
D : 그거 영업 팀에서 분석해야 하는거 아닌가?
A : 이전 프로젝트 결과를 토대로 우리가 추가해줬음 하는 것도 있어서요.
B : 그런걸 요구사항이라고 부를 수 있나요?
A : 엇비슷한 프로젝트니까 일단은 그렇게 하고 프로젝트를 진행해달라는 게 '영업팀의' 요구사항이죠.
B : 왜?
C : 받아온 프로젝트 기간이 좀 빡빡해서요.
(2달 후)
A : 우리가 구현한 기능들 중 한 열개 항목 정도가 잘못되었다는 군요.
B : 누가 그래?
A : 고객이요.

보통 개발자들이 고객을 미워하는 이유는 개발자들이 보기에 고객이란 사람들이 '뭘 잘 모르는 사람들'처럼 보이기 때문도 있습니다만, 가장 큰 이유는 프로젝트 진행 중에 요구사항을 변경하는 사람들이 대부분 고객이기 때문입니다. 그런 일이 자주 생기면, 개발자들의 인생은 급속도로 피폐해집니다.

그런데 사실 그건 고객 잘못은 아니에요. 위의 '흔히 보는 장면' (진짜로 이런 일이 흔하면 큰일나겠습니다만 ㅋㅋ)에서도 느끼실 수 있겠지만, 애초에 프로젝트를 진행하는 팀이 고객과 의사소통을 할 기회가 없었다는 것이 가장 큰 문제죠. 자뻑용 프로젝트를 한다면야 모르곘지만, 개발팀을 고객과 떼놓고 프로젝트를 하면 득보다는 실이 더 많아질 수도 있습니다. 고객/사용자는 종종 시장 그 자체이며, 시장의 변화를 외면하면서 시장에 '최선의 가치를 전달할' 방법 같은 것은 없습니다. 시장의 변화라는 것은 곧 '불확실성'이기 때문에, 우리는 어떻게든 불확실성을 끌어안을 방법을 찾아내야 합니다.

애자일 방법론이 의사소통의 문제를 가끔 비 이성적으로 보일 때 까지 깊이 탐구하는 것은 아마 그래서 일 겁니다. 애자일 방법론은 구두로 나눈 대화와, 그 자리에서 합의된 사항을 '개발 계획서'보다 더 중시하는 경향이 있습니다. (이 점이 전통적인 방법론을 옹호하는 사람들의 심기를 굉장히 불편하게 만들곤 합니다만...) 이건 제 개인적인 경험입니다만, 지식을 늘리는 가장 간단한 방법른 사실 구글 검색도 아니고 독서도 아닙니다. '대화'죠. 대화가 확대할 수 있는 지식의 영역은 특별히 어딘가로 제한되지 않습니다. 하지만 프로젝트 진행에 있어 가장 도움되는 부분은 다음의 몇 가지 일 것 같군요. (무순)

1. 프로젝트 자체에 대한 지식
2. 팀원에 대한 지식
3. 고객/사용자에 대한 지식

앞서도 언급했었습니다만, 지식이 증가하면 불확실성은 감소합니다. 지식을 늘리는 방법으로 스파이크(spike) 같은 것도 있습니다만, 아무래도 고수와의 커피타임만큼 짧고 효율적이진 않습니다. 요구사항 변화에서 오는 야근을 줄이는 방법으로 '무조건적인 퇴근시간 준수'같은 것도 있겠습니다만, 아무래도 요구사항을 낸 이해 당사자와의 전화통화만큼 효과적이지는 못합니다.

의사소통을 자주 그리고 효율적으로 하게 되면 프로젝트 중반에 발생하는 이런 저런 일들에 보다 능동적으로 대처할 수 있게 됩니다. 그리고 팀이 지금 어디쯤 와 있는지 좀 더 분명히 알 수 있게 되고, 앞으로 발생할 지 모를 '꼭지도는' 일들이 팀의 항로를 어떻게 바꾸어 놓게 될지 좀 더 명료하게 알 수 있게 됩니다.

의사소통을 강조하는 것이 자기 파티션 안에 처박혀 있기를 좋아하는 개발자들의 습성과 배치되긴 합니다만, 그런 개발자의 모습은 개발이라는 것을 종종 예술(art)과 비슷한 것으로 묘사하는 너드(nerd) 문화의 클리셰일 가능성이 높습니다. 개발자들은 프로그래밍이 오타쿠들이 좋아하는 저패니메이션같은 것과는 질적으로 다르다는 사실을 깨달을 필요가 있어요. 개발은 현실이고, 현실은 가혹합니다. 개발자들이 의사소통을 무시하고 파티션 안에 처박혀 있으려는 습성은 어찌보면 개발자들이 처한 현실이 주는 문제를 계속해서 재생산하는 측면이 있습니다. 그냥 그 안에 처박혀서 '사용자들은 아무것도 몰라'라고 해서는 인간다운 생활을 영위할 수 없다는 것이 문제라는 거죠. 불확실성을 감소시키지 않으면, 야근 일수는 줄어들지 않는다, 뭐 그런 이야기죠.

하지만 굳이 불확실성 이야기의 외연을 개발자 각각의 삶으로 넓혀놓지 않더라도, 불확실성을 감소시키는 것은 중요합니다. 소프트웨어 프로젝트의 목표가 '고객에게 최선의 가치를 전달'하는 것이라고 보면, '최선의 가치'라는 것의 정의는 시간이 지나면 어떻게든 변화하게 될 처지이니 (시장 상황은 자고 일어나면 바뀌죠) 불확실성이라는 것이 존재한다는 사실을 인정하고 그것과 '화해할 수 있는' 전략을 수립하도록 애쓰는 것이 프로젝트를 관리하는 사람이 해야 하는 일이 아닐까요?


Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008. 8. 31. 00:47
저는 남들이 뭘 질렀더라 하는 소리에 별로 영향을 안받는 부류의 인간입니다. 신제품에 필이 꽂혀본 적이 별로 없다는 뜻도 되겠습니다. 하지만 그런 저도 충동적으로 지르는 물건이 한 종류 있습니다.

바로 카메라죠. -_-

하지만 카메라를 구매할때도, 엔간하면 이성적으로 행동하는 편입니다. 물론, 사진같이 돈이 많이 들어가는 취미생활을 하는 것 부터가 '비이성적'인 것 아니냐, 라고 하실 분도 계실 줄 압니다. 하지만 한가지는 유념하도록 합시다. 재정적으로 다소간 비합리적으로 행동한다고 해서, 꼭 그게 비이성적인건 아닙니다. 재정적 합리성이 이성적이냐 아니냐를 가름하는 척도는 아니니까요.

각설하고, 저는 사진기를 살때도 '신제품이 나오면 바로 지르'지는 못합니다. 소심해서 그런걸까요? 머리 털나고 신제품을 지른 적이 딱 한번 있는데, 바로 istDs를 살때였습니다. (나중에 굉장히 후회했습니다. 이유는 복잡해서 말씀드리기 좀 곤란합니다.) 그 때 이전과 이후로는 단 한번도, '신제품을 지른'적은 없습니다.

사실 DSLR은 가전제품과 여러가지로 비슷합니다. 가전제품은 신제품이 나오면 그 가치를 '급격하게' 상실하는 경향이 있습니다. 신제품에 '언제나 바라던 바로 그 기능'이 '더 강하게' 그리고 '더 많이' 탑재되어 있는 경우가 많거든요. 거기다 요즘 가전 제품들은 신제품이라고 해서 구형 제품보다 엄청나게 비싼 건 아니기 때문에, 구형 제품은 가격하락->단종->중고가격하락의 수순을 아주 빠르게 거치게 됩니다. 요즘은 DSLR이 딱 그 꼴입니다.

요즘 DSLR의 경우, 새 카메라는 예전 카메라보다 '더 나은 화질'을 보장합니다. 필름 카메라 시절에는 그렇지 않았습니다. (어떤 카메라던, '필름'을 써야 한다는 점에 있어서는 같았으니까요.) 물론 새 카메라를 쓰면 '더 나은 품질의 사진을 얻을 수 있는 확률'이 좀 높아지긴 했습니다.. (AF 정밀도가 상승한다던가 하는 이유로 말입니다.) 하지만 DLSR 시장에서 '더 나은 화질'은 소비자의 마음을 흔드는 결정적인 요소입니다. 새로 시장에 출시되는 카메라들은 '더 적은 노이즈', '더 높은 감도', '더 많은 화소수' 등을 무기로 내세우지 않으면 안되었습니다. 결국, '화질'이 '선형 기능'이 된 셈이고, 그 선형 기능들을 앞세워 DSLR 시장이 커진 셈입니다.

그런데 DSLR이 이런 식으로 가전제품화되자, 저같은 사람들은 좀 유리해졌습니다. 한 삼사년 묵은 중고 카메라를 느긋하게 기다렸다가 사면 거의 1/4가격에 살수 있거든요. '국전에 나갈것도 아닌데 최신 카메라는 좀...' 뭐 이런 생각인겁니다. 물론, 안좋은 점도 있습니다. 카메라를 3~4년 뒤에 산다고해서 새로 나온 카메라에 대한 정보들까지 끊고 사는 것은 아니거든요. 결국 눈높이가 높아지게 되는데, 그럼 설사 저렴한 가격에 카메라를 구매하더라도 만족하지 못하는 경우가 생기게 됩니다. 화질 때문에 그렇죠.

* * *


카메라를 팔아버리고 허전함에 견디다 못해 D70s를 25만원이라는 헐값에 업어왔습니다. (아 이러면 안되는데.. ㅎㅎ) 이 카메라는 여러모로 제가 전에 쓰던 카메라보다 '기계적으로' 좋습니다. 1/8000의 셔터스피드를 지원하고, 펜탁스 유저로서는 '상상하기 힘든 스피드와 정확도로' AF를 잡습니다. -_- 하지만 화질로 보면? 글쎄요. ISO 400에서의 노이즈는 펜탁스보다 화끈하고 -_-; 화벨은 지랄맞습니다.

선형 기능이 선형 기능으로써 한 몫을 제대로 하려면, 한물간 중고품이 해당 제품의 대체제로 기능하기 어려워야 합니다. 그런 의미에서 보면, 현재의 DSLR 시장에서의 선형 기능은 분명 화질입니다. 2~4년전의 저렴한 중고품을 사용해서는 화질 측면에서 지금과 같은 만족을 얻을 수는 없거든요. 하지만 앞으로도 그럴까요? 그건 알수가 없습니다.

시장이 Full Frame으로 달려가고 있습니다만, 많은 아마추어 사진가들에게 천만화소 이상의 Full Frame 사진은 사실 부담입니다. 그정도 되면, 사진을 보관하고 백업하는 것에 대해서 심각하게 고민을 해봐야 하죠. 많이 찍어대는 사람이라면 500G 하드로도 부족할지도 모릅니다. ㅋㅋ

거기다 이미지 처리 방식이 어느 수준 이상에 도달하면, 사람들은 더 많은 돈을 투자해 새 제품을 사봐야 그만큼 좋은 화질의 사진을 얻을 수 없다는 깨달음을 얻게 될수도 있습니다. (그런 시점이 생각보다 빨리 올것 같기도 합니다.) 그때쯤 되면, 지금의 DSLR 중고 시장은 다시 필름 카메라 시장처럼 되겠죠. '오래된 명기'가 대접을 받고, 제품이 단종되더라도 가격이 지금처럼 빨리 떨어지지는 않는, 그런 시장 말입니다.

어쩌면 그래서 올림푸스를 필두로 한 많은 카메라 메이커들이 '선형 기능' 대신 '감동 기능'을 팔아먹기 위해 그토록 매진하는지도 모릅니다. 이번에 니콘에서 D90을 내놓으면서 DSLR로 동영상을 찍을 수 있다는 것을 밀어부치고 있더군요. 거기다 가격은? 80만원대랍니다. ㅎㅎ

* * *


그냥 글을 맺으면 돌맞을것 같으니까 이번에 갑사에서 D70s로 찍은 사진 몇장 올려놓도록 하죠. ^^;
가로사진은 누르면 커집니다.

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지



Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008. 8. 7. 10:08
제품을 개발할 때, 그 제품에 탑재될 기능을 흔히 필수 기능(mandatory features), 선형 기능(linear features), 감동 기능(delighter) 등으로 구분하곤 합니다.

필수 기능은 제품에 반드시 탑재되어야 할 기능, 즉 제품을 시장에 내놓으려면 반드시 들어가야 하는 기능이고,  선형 기능은 많이 들어가면 들어갈수록 고객의 만족도가 늘어나는 기능입니다. 감동 기능은 고객이 기대하지는 않는 기능입니다만, 일단 그런 기능이 있다는 것을 알면 감탄해 마지 않을만한 기능, 기꺼이 그 기능을 위해 지갑을 열만한 기능을 일컫습니다.

최근에 모니터/키보드/마우스 공유기가 필요해서 어떤 제품들이 있는지 인터넷을 뒤져서 좀 살펴봤었습니다. 과거에는 PS2 방식의 공유기가 대세였습니다만, 요즘은 USB 키보드와 마우스가 많아지는 만큼, USB 공유기도 등장하고 있습니다. 필수 기능이 PS2에서 USB쪽으로 이동하고 있다는 증거입니다.

이런 공유기에서 선형 기능은 아마도 공유할 수 있는 모니터/키보드/마우스의 개수일 것입니다. 공유 포트가 많아지면 많아질수록 고객의 만족도는 증가합니다. 물론 그에 비례해서 가격도 증가하기 때문에 고객의 만족도가 이 경우에는 반드시 선형적으로 상승한다고 보기는 좀 어려울 수도 있습니다. 하지만 이렇게 생각해보죠. 고객의 만족도가 증가하면 고객은 그 기능을 위해 기꺼이 지갑을 열고 돈을 지불하게 될 수 있습니다. 그러니 제품을 개발할 때 선형 기능에 대해 생각해 보는 것은 반드시 필요하죠.


사용자 삽입 이미지

그렇다면 만족 기능들로는 어떤 것을 생각해 볼 수 있을까요? 벨킨이라는 업체에서 내놓은 공유기 중 한 모델은 제품을 세워서 놓을 수 있도록 배려하고 있을 뿐 아니라 (공간을 덜 잡아먹습니다) 다른 제품들보다 미려한 외관을 자랑합니다. (최근에는 디자인 적인 요소가 감동 요인으로 등장하는 경우가 늘고 있습니다.) 거기다 이 제품은 여벌의 USB 포트를 제공해, 컴퓨터간에 공통의 USB 장비를 공유할 수 있도록 하는 기능도 제공합니다. USB프린터, USB 메모리 등이 이런 범주에 들 수 있겠죠. 잘만 써먹으면 USB 메모리를 통해 WIndows와 Linux간에 데이터를 아주 손쉽게 공유할수도 있습니다. ㅋㅋ

그런데 이것 말고 다른 감동 요인은 없는 걸까요?

제 책상에는 두 대의 모니터가 있습니다. 한 모니터는 Windows 머신에 연결되어 있고, 다른 한 모니터는 Linux 머신에 연결되어 있습니다. 두 모니터를 동시에 봐야 하기 때문에, 공유기로 연결하더라도 모니터는 연결할 필요가 없습니다. 오직 키보드와 마우스만을 공유해야 하기 때문에, 처음에는 Synergy라는 소프트웨어를 깔아서 키보드와 마우스를 공유하려고 했었습니다.

그런데 문제가 있더군요. 제 컴퓨터들에서는 어쩐 일인지 이 프로그램이 너무 느리게 돌아가는 겁니다. ㅋㅋ Synergy의 가장 큰 장점은, 키보드와 마우스가 두 개의 컴퓨터 사이에 Seamless하게 연동될 수 있도록 만드는 것입니다. 마우스가 윈도우 화면의 가장자리로 이동하면 그 포인터가 자동적으로 Linux 쪽 화면으로 이동하고, 키보드 제어도 자동적으로 그쪽으로 넘어갑니다. 너무 편하죠.

하지만 일반적인 공유기를 사용하면 이런 장점을 누릴 수 없습니다. 모니터를 공유기로 연결하지 않으면 두 화면을 동시에 볼수는 있을텐데, 키보드나 마우스 제어를 다른 컴퓨터로 옮기려면 단축키를 누르거나 공유기의 버튼을 눌러줘야만 합니다.

그렇다면, Synergy가 가지고 있는 기능을 제공하는 공유기가 있다면, 그것이 감동 요인이 될 수 있지 않을까요? 물론 별도의 프로그램을 깔아야 할 수도 있겠고 공유기의 가격도 올라갈 수 있겠습니다만, 두 모니터로 보는 화면이 마치 한 화면처럼 통합될 수 있다는 점에서 (운영체제가 다르고 시스템도 다름에도 불구하고!) 아주 좋은 공유기가 될 수도 있을텐데 말이죠. 저는 아직 이런 제품을 못 찾았습니다. 직접 만들어볼까도 생각해봤는데, 아시다시피 제가 HW쪽으로는 아는게 없어서 말이죠.

Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2008. 5. 21. 17:20

찾아보니까 세상에는 굉장히 많은 단위 테스트 툴들이 있더군요. jUnit은 그 중 가장 유명하다 할만 합니다. 그런데 이런 단위 테스트 툴들이 없었을때에는 도대체 어떻게 단위 테스트를 했을까요? -_-

C를 위한 단위 테스트 프레임워크로 Check라는 것이 있습니다. (세상에 존재하는 모든 단위 테스트 프레임워크의 목록을 보고 싶다면 www.xprogramming.com의 관련 페이지에 가보시는게 좋겠군요.  저도 그 중 몇 가지 단위 테스트 프레임워크는 써 봤구요.) 배워볼까 싶어 잠깐 훑어봤는데, 이런 생각이 들더군요.

단위 테스트를 하기 위해서 꼭 특정한 테스트 프레임워크의 사용법을 배워야 한다면, 좀 귀찮지 않을까요? 제가 배우고 있는 언어만도 Java, C++, C, Ruby, Erlang, Scheme 등등 많은데, 그럼 대체 몇 가지의 테스트 프레임워크를 설치해야 하나요?

요즘은 Eclipse 덕에 이런 저런 프로그래밍 언어들을 동일한 환경에서 사용하기가 편해졌다고는 합니다만, 가끔은 환경 설정하고 프로그램 설치하고 하는 것도 귀찮을 때가 있거든요. 특히 시간이 부족해서 후달릴때는 더더욱.

그래서 이번에는 그냥 assert만 사용해서 단위 테스트를 해 봤습니다. 뭐 CppUnit 써서 테스트 할 때와 엇비슷한 코드가 나오더군요 ㅎㅎ (제가 훌륭한 프로그래머가 아니라서 그럴지도 -_-)

개략적인 코드 얼개만 잠시 보시면...

....    // 열심히 짠 C 모듈 코드.

#ifdef _TEST

/*
 * test DRIVER program. Just to test this module.
 */

HashTableBlock hash_table;
HashTableEntryBlock hash_table_entries;

int main() {
    ...
    HashTableEntry* e1 = ...;
    assert( e1 != 0 );
    ...
    HashTableEntry* e3 = ...;
    assert( e3->src_addr == 0x7f000001 );
    assert( e3->src_port == 3086 );
    ...

    int i = 0;
    for( i = 0; i < 1050000; ++i ) {
        HashTableEntry* ee = alloc_hash_entry_block(&hash_table_entries);
        assert( ee != 0 );
    }
    return EXIT_SUCCESS;
}

#endif

#ifdef와 #endif를 사용했기 때문에, test driver 코드는 _TEST가 명시된 경우에만 생성됩니다.

원래는 Check를 써서 테스트를 할까 했는데, 직접 assert를 사용해서 테스트를 해 봐도 뭐 그렇게 테스트가 어려워진다거나 하지는 않는 것 같아요. 결국 취향 문제인데, 테스트 결과를 일목요연하게 정리해서 보여준다거나 하는 기능이 정말로 아쉬운 사람이라면 assert 대신 다른 테스트 프레임워크를 사용하는 것이 낫겠지만, 그런 리포트에 연연하지 않는 사람이라면 이렇게 해도 별 상관 없을 것 같아요.

어차피 단 1개의 테스트만 실패했더라도, 실패한 것은 실패한 것이니까요.

[여기까지 작성한 다음에 출장을 다녀옴 -_-]

위와 같이 assert를 사용한 단위 테스트 방안을, 커널 모듈을 테스트하는 데 응용해 봤습니다. 원래 커널 모듈을 컴파일하기 위해 제가 사용했던 Makefile은 대략 다음과 같이 생겼습니다.

obj-m := captureapp.o

clean:
    \rm -f *.ko *.o *.mod.c


좀 심하게 단순하죠 -_-; 최상위 단계가 clean인 덕에, make를 때리나 make clean을 때리나 효과는 똑같습니다. 아무튼, 이 Makefile을 다음과 같이 바꾸었습니다. (2008년 5월 26일 수정됨)

obj-m := captureapp.o

UNITTEST_SRCS = hash_table_test.c
UNITTEST_EXES = ${UNITTEST_SRCS:.c=}

unittest : ${UNITTEST_EXES}

${UNITTEST_EXES} : ${UNITTEST_SRCS}
    gcc -D_TEST $< -o $@

clean :
    \rm -f *.ko *.o *.mod.c ${UNITTEST_EXES}


따라서 make unittest를 때리면, 단위 테스트를 실행하는 실행파일들이 테스트 타겟별로 만들어지게 됩니다. 위의 경우에는 현재 테스트 타겟이 hash_table 밖엔 없어서 hash_table_test라는 실행파일만 만들어지게 되죠.

hash_table.h에는 테스트 타겟인 hash table관련 코드들이 들어가있게 되구요. hash_table_test.c에는 그 코드들에 대한 단위 테스트 루틴들이 들어가게 됩니다. 우선 hash_table.h를 보시면, 코드 윗부분에 다음과 같은 매크로 디렉티브들이 들어가 있습니다.

#ifdef _TEST
#include <stdint.h>
#include <assert.h>
#include <string.h>
#define u32 uint32_t
#define u16 uint16_t
#define u8 uint8_t
#else
#include <linux/module.h>
#define assert(x)
#endif

hash_table.h의 코드가 커널 모듈에 들어갈 코드이긴 하지만, 그 코드가 커널 루틴을 심하게 건드리는 코드가 아닌 단순히 커널 모듈 안에서 사용될 라이브러리 코드라면, 사용자 공간에서 테스트를 하는 것이 낫거든요. 그래서, 위와 같은 매크로 디렉티브들을 사용하여 커널 코드에서 사용되는 타입과 사용자 공간에서 사용되는 타입들을 일치시킵니다. assert는 커널 모듈안에서는 사용할 수 없으니까 제거해주는 부분도 넣었구요.

이제 hash_table_test.c를 보시면 되겠군요. 만일의 경우를 대비해 hash_table_test.c의 코드 대부분은 #ifdef _TEST ... #endif 사이에 정의됩니다. 앞서 보셨던 코드와 별반 다를 것은 없습니다.

#include <stdlib.h>
#include "hash_table.h"

#ifdef _TEST

/*
 * test DRIVER program. Just to test this module.
 */

HashTableBlock hash_table;
HashTableEntryBlock hash_table_entries;

int main() {
    ...
    HashTableEntry* e1 = ...;
    assert( e1 != 0 );
    ...
    HashTableEntry* e3 = ...;
    assert( e3->src_addr == 0x7f000001 );
    assert( e3->src_port == 3086 );
    ...

    int i = 0;
    for( i = 0; i < 1050000; ++i ) {
        HashTableEntry* ee = alloc_hash_entry_block(&hash_table_entries);
        assert( ee != 0 );
    }
    return EXIT_SUCCESS;
}

#endif


적고보니 단위 테스트를 '프레임워크 없이'도 좀 체계적으로 진행하려면 준비해야 할 것이 꽤나 많은데요. 아마 단위 테스트 프레임워크를 사용하는 이유 중 하나는 이런 체계를 어떻게 만들어야 할 지 감이 잘 오지 않기 때문이겠죠. 만들고 나면 사실 별거 아닐 수도 있습니다만...

Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008. 3. 27. 12:06
최근에 바쁜 일도 끝나고 해서 (아직 논문 실험 데이터 뽑는 일이 남긴 했습니다만 ㅋㅋ) 이 책을 다시 잡았습니다. 제 블로그에 자주 오시는 분은 (계실지 모르겠습니다만) 아시겠습니다만, 잠시 놓고 있었던 "생각하는 프로그래밍"도 다시 읽고 있습니다.

애자일 회고를 읽으며 한 가지 감탄했던 것은, 회고라는 행위 자체도 Agility의 영역에서 자유롭지 않다는 점이었습니다. 나부군님의 이야기를 한번 읽어보죠.

[전략] 다만 회고를 진행하는 목적을 잊지 말아야 한다. 팀이 프로세스를 개선하고 같은 문제점을 반복하지 않기 위해서 회고를 진행하는 것임을 잊지 말아야 한다. 회고 자체가 목적이 되어서는 안된다.막연한 기대를 품고 사람들이 모두 지겨워하고 괴로워하는 회고를 억지로 이끌어나가는 팀에게 개선은 매우 힘든 일이 될 것이다. 회고 자체도 개선해야 한다. 뭔가 문제가 있다면, 진행 과정을 더욱 간단하고 쉽게 만들자. 사람들이 최대한 부담을 느끼지 않는 선에서 시작해서 발전해 나가는 방식도 좋다. 처음 회고를 진행하다보면 많은 난관에 부딪힐 수 있다. 이때 회고 자체를 개선할 수 있다는 마음가짐으로 회고에 대한 회고를 반복하다보면 결국 팀이 개선하는 데도 큰 도움이 될 것이다. [후략] (191페이지)
회고를 회고하고 개선한다... 결국 모든 것은 개선의 대상이라는 뜻 되겠습니다. 애자일 지침들에서 가장 마음에 드는 것 중 하나는, "어떤 것도 처음 그 상태로 내버려두지 않는다"는 것입니다. 결국 프로젝트의 목적이란 불확실성을 프로젝트에서 조금씩 덜어나가는 것일 터. 불확실성을 제거하는 것은 좋은 제품을 만드는 데도 도움이 되고, 더 나은 팀을 만드는 데도 도움이 되며, 더 나은 일상생활을 영위하는 데도 도움이 됩니다. (웃음) 어쩌면 프로그래머가 추구해야 할 궁극적인 지점은 바로 그 부분이 아닐까요? 더 나은 삶을 만드는 것.

사용자 삽입 이미지

더 나은 삶을 만들기 위해서는, 단순히 프로그래밍 기술을 뛰어 넘는 어떤 것이 필요합니다.

아마도 여러분은 현재 각자 일하고 있는 분야에서 전문가일 것이다. 하지만 회고를 매끄럽게 진행하는 업무를 하기 위해서는 소프트웨어 분야와는 다른 기술과 시각이 요구된다. 새로운 기술을 연마하는 데는 많은 시간과 연습이 필요하다. 마음의 여유를 가지고, 원하는 바가 무엇인지 잘 정리하고, 스승을 찾아라. 여러분은 자신을 발전시키는 일에도 '조사하고 적용하게 하기'를 사용할 수 있을 것이다. (82페이지)
분명 애자일 프로세스도 기술입니다. 하지만 여태껏 알아왔던 어떤 프로세스도, 프로세스에 참여하는 사람의 삶의 질을 어떻게 향상시킬 것인지에 대해서는 언급하지 않았던 것 같아요. 주로 예측 가능성의 문제에만 집중해 왔죠. 반면 애자일 프로세스는 프로젝트에 참여하는 사람들이 어떻게 협동하면 보다 나은 무엇이 만들어질 수 있는지 자주 이야기합니다. 회고도 그 중 하나예요.

애자일 프로세스 전반에 큰 관심이 없더라도, 이 책은 한 번 읽어볼 만 하리라고 생각합니다. 회고라는 것은 결국 '과거에 했던 어떤 것'을 돌아보는 행위일텐데, 세상에 과거 따위는 없다고 말하는 프로세스는 없거든요.


Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008. 2. 29. 12:11
뻔한 이야기가 되겠습니다만, 프로그래머도 다른 직장인과 다를 것이 없는 사람입니다. 휴식이 필요한 존재죠. 하지만 프로그래머들은 잘 쉬질 못합니다. 특히, 잘 가다듬어진 프로젝트 환경이나 업무환경을 가지지 못한 상태에서 일하는 프로그래머들은 더더욱 그렇습니다. 거기다가, 어떤 프로그래머들은 스스로 휴식을 거부하는 경향을 보이기도 합니다. 이런 증상을 보통은 업무 중독증이라고 부르기도 하죠.

지난 한주 괌 PIC에 다녀왔습니다. 가끔 가까운 데 아이들을 데리고 놀러갔다 올 때는 있었습니다만, 제 돈을 주고 이렇게 오랫동안 쉬어본 것은 처음입니다. 그래서 그런지, 사박오일동안 많은 생각이 들더군요.

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지

제가 프로그래머로서 코드를 잡고 일을 한 지가 벌써 십수년 가량이 흘렀습니다. 그 동안, 저는 의식적으로 휴식을 회피하면서 살았습니다. 거기에는 제가 하는 일의 성격이 한 몫을 했습니다. 사실 프로그래머라는 사람들은 컴퓨터가 놓인 자리에 앉아 하루의 대부분을 보내죠. 단기적으로 보면, 대단한 육체적 강인성을 요구하는 직업도 아닌데다, 손가락 놀릴 힘만 있으면 그다지 큰 피로감없이 하루 종일을 컴퓨터 앞에서 보낼 수도 있습니다. 생각해 보니까, 저도 최근 한 십년 가량은 거의 그런 상태로 살았던 것 같습니다.

예전에 마이크로소프트웨어라는 잡지를 구독했던 적이 있는데요. 초창기 기사중에 프로그래머의 책상 위 풍경을 묘사한 대목이 있었습니다. 아무렇게나 구겨져 뎐져 있는 서류 뭉치, 필요할 때면 아무때나 손을 뻗어 마실 수 있는 음료수, 키보드, 마우스, 매뉴얼, 그리고 플로피 디스크들... 사실 지금도 대부분의 프로그래머들은 이런 책상 앞에서 일을 하고 있을 겁니다. 프로그래머들의 생활 패턴이라는 것이 그다지 많이 바뀌지 않았으니까요. 프로그래밍이라는 것이 갖는 창의적인 속성이 큰 만큼, 그 앞에서 시간을 보내는 것이 가장 재미있는 일이라는 생각을 가지고 있는 프로그래머들이 많습니다. 그리고 그렇게 시간을 보내다 보면 책상은 여지없이 어지러워지고 말지요.

그런데 이런 생활을 오랫동안 하다 보면, 프로그래밍 이외의 다른 모든 활동들에 대한 자신의 태도가 좀 이상하게 바뀌게 된다는 문제가 있습니다. 가령 일을 하다가 하루 쉬어도 되는 날이 생겼다고 칩시다. 그런데 그 쉬는 날이 어쩐지 영 탐탁치 않게 느껴진다는 겁니다. 안그래도 업무가 바쁜데, 하루를 쉬면 그 업무를 좀 더 빨리 처리할 수 있을 시간을 날려버리게 되는 것 같이 느껴지는 것이죠. 그러다 보면, 다른 사람들이 전부 휴가를 내고 자리를 비웠다 할지라도, 회사에 나와 업무를 보게 되는 일이 많아집니다. 팀원 중에 누군가가 하루 같이 시간을 내서 어디 바람이라도 쐬러 가자고 하면, "이렇게 바쁜데 놀자니, 제정신인가?"하는 마음이 들어 기분이 좋지가 않죠. 어쩐지 그 사람은 팀 전체 생산성에 악영향을 주는 사람 같이 느껴지기도 합니다.

이 이야기는 제 이야기이기도 합니다. 저는 이런 생활을 십여년 동안 하면서, 단 한 번도 제 자신이 '소비되고 있다'는 생각을 한 적이 없습니다. 아마 거기에는 '하루라도 빨리 돈을 벌어야 한다'는 식의 경제적 강박관념과, '이렇게 놀고 있어서는 분명 뒤쳐질 것이다'라는 불안감도 한 몫을 했을 것입니다. 하지만 이제 37세가 된 제 자신을 돌아보면, 저는 분명 제 자신을 조금씩 갉아먹었던 것 같습니다. 체중은 조금씩 늘었고, 머리는 희끗희끗해지기 시작했으며, 젊은 나이에 녹내장을 앓고 있고, 속이 좋지 않아서 탄산음료나 차가운 물은 거의 엄두도 내질 못합니다. 그리고 건강 문제는 제쳐두고라도, 저는 그렇게 살아온 시간 동안 스스로 기꺼워할만한 멋진 업적 하나도 만들어 내질 못했습니다.

진부한 이야기일지 모르지만, 휴식은 재충전의 기회입니다. 인간에게도 충전지와 비슷한 구석이 있어서, 재충전이 없이는 이전 수준으로 생산성을 되돌리는 일이 불가능 할 때가 생깁니다. 잠을 자지 않고서도 위대한 업적을 만들어 낼 수 있겠지만, 그런 상태는 오래 지속될 수 없습니다. 거기다, 쉬지 않는 사람은 나중에 감당하기 어려울 정도로 큰 문제를 만들어 내기도 합니다. 술을 마신 상태에서 만든 코드가 나중에 너덜너덜해지는 것과 비슷한 이치죠.

휴식은 어떻게 보면 "나는 내 자신에게 얼마나 관대한가"의 지표이기도 합니다. 자신에게 관대하지 않은 사람은, 타인에게도 관대할 수 없습니다. 타인에게 관대하지 않은 사람은 종종 팀웍을 해치고, 그런 사람들은 자신도 모르는 사이에 팀 전반의 퍼포먼스를 떨어뜨리기도 합니다. 저는 지난 사박오일간, 지난 십년간 제가 제 자신에게 얼마나 무자비했던가를 깨달았습니다. 앞으로 프로그래머로 일할 날이 몇 년이나 더 남아있을지는 알 수 없습니다만, 앞으로는 제 자신에게 좀 더 관대해 져야 할 것 같습니다.

다만, 코드에게까지 필요 이상으로 관대해질 필요는 없겠죠.
자신에게는 관대하게, 그리고 코드에게는 냉철하게.


Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 맞습니다. 충분한 휴식은 더 좋은 코드를 생산해 낼 수 있는 원동력이지요. ^^

    2008.02.29 12:44 [ ADDR : EDIT/ DEL : REPLY ]
  2. 전 몇 시간 코딩을 하다가 만나는 문제는
    몇 시간 더 투자하여 머리를 싸는 것보다는
    잠시 휴식을 취하거나 잠을 자고 다음 날 풀어보면 깔끔하게 풀리더군요.
    거기에 그 때 실수한 것들을 찾아내고 최적화까지...;;;;
    하지만 저도 '조금만 더 하면 되겠는데....', '쉬는 것보다는 나아가는 것이 더 좋잖아.'라는 강박감이 있습니다.OTL..

    2008.02.29 14:38 [ ADDR : EDIT/ DEL : REPLY ]
  3. morison

    휴식.. 정말 중요하죠...
    그런의미에서 이런글도 좋지만
    괌의 멋진 풍경 사진이라도 한번 올려주셔 보시죠^^

    2008.02.29 17:35 [ ADDR : EDIT/ DEL : REPLY ]
    • 이 글 쓸때는 사진이 집에 있었거든요. ㅋㅋ
      나중에 올려보겠습니다.

      2008.02.29 17:50 [ ADDR : EDIT/ DEL ]
  4. 저도 휴식이 참 중요하다는걸 새삼 느끼고 있답니다...
    (아직 학생이긴 하지만..)
    이제는 코딩이 잘 안된다는 생각이 들면 딱 접어야겠습니다.
    다음날 아침에 맑은 정신으로 다시 오는 것이 좋습니다.
    또한 밤늦게까지(또는 밤새는거) 예전부터 너무 싫어했고요.
    (하지만 함께 해야 될때는 올빼미 스타일의 사람들이 너무 많아서 저 너무 괴로워요 ㅠㅠ)

    2008.02.29 22:19 [ ADDR : EDIT/ DEL : REPLY ]
    • 그러게요~ 같이 일하는 사람들 스타일이 다르면
      어쩔수 없이 따라가야 하는 일도 생기죠.

      2008.03.01 08:23 [ ADDR : EDIT/ DEL ]
  5. 아이의 웃는 모습을 보니 괜시리 기분이 좋아지는군요.. :)
    아이가 크고 나면 같이 놀아주고 싶어도 애가 놀 시간이 없어진다고 일부러 시간은 못낼 망정 주말만이라도 같이 놀아줘라라고 호통치던 아내의 말이 문득 떠오릅니다.. 휴식은 자기만의 충전이 아닌 가족과의 커뮤니케이션의 다른 모습일 수도 있겠다는 생각을 해봅니다..

    2008.03.03 14:54 [ ADDR : EDIT/ DEL : REPLY ]
  6. 우어... 정말 좋은글을 보고 갑니다.

    저도 요새 이런 생각을 하고 있었거든요.
    연구실에서 하루종일 매달려서 알고리즘 개발하고
    실험하고 뭔가 테스트 해보고 정신없이 무엇인가 하다가
    막상 휴일이 되었을때, 내가 지금 쉬면 남들보다 뒤쳐질텐데 라는 생각을...
    그래서 휴일에도 어김없이 나와서 뭔가 새로운걸 보고 연구하고 있었습니다.

    하지만 이 글을 보니 다시 한번 생각해봐야겠군요.
    저에게 있어서 제대로된 휴식이 주어지지 않은지가
    매우 오래 되었다는걸 잊고 있었습니다.
    다시금 저를 되돌아보도록하게 해주는 글이군요. 감사합니다 ^^;

    2008.03.11 00:15 [ ADDR : EDIT/ DEL : REPLY ]
    • 재미있게 읽어주셔서 감사합니다.
      휴식은 모든 사람에게 필요한 부분인데,
      정작 잊고 살게 되는 경우가 많지요.
      우리 모두 좀 쉬면서 삽시다. ㅋㅋ

      2008.03.11 00:46 [ ADDR : EDIT/ DEL ]

Extremely Agile/TDD2008. 2. 17. 11:28
프로그래밍을 하다가 만나게 되는 문제들 중 상당수가 (특히 C/C++ 프로그래머의 경우) 메모리 문제입니다. 메모리 문제는 찾아내기도 어렵고, 교정하기도 어렵습니다. 잘못된 메모리 사용이 실제 어떤 형태의 현상으로 드러나게 될지를 단언할 수가 없는 탓입니다. 특히 Java같은 언어는 메모리를 할당하는 과정은 프로그래머가 통제할 수 있지만, 메모리를 반환하는 과정은 통제할 수 없기 때문에, 메모리 문제를 발견하기도 어렵고 교정하기는 더더욱 어렵습니다. 그래서 Java 프로그래밍을 하는 와중에 험한 꼴을 당하지 않으려면 Effective Java같은 책을 잘 읽어야 합니다. X-)

C/C++ 프로그래밍 언어의 경우에는 메모리를 조작하는 데 있어 프로그래머가 갖는 자유도가 꽤 큽니다. 그래서 메모리를 엉뚱하게 조작하는 실수를 저지를 확률이 굉장히 높은 편입니다. 그래서 일찍부터 많은 사람들이 'Unix 계열 운영체제에서 C나 C++로 프로그래밍하는 사람들을 위한' 메모리 관련 문제 탐지 기법들을 내놓았습니다. 이런 기법들은 '잘못된 메모리 참조가 발생할 경우 그 사실을 보고해 주는' 형태를 띠고 있으며, 워낙 그런 기법에 대한 수요가 컸기 때문에 그 일부는 이미 운영체제에 붙박이로 제공되고 있기도 합니다.

가령 Linux 같은 경우는 bash 상에서 MALLOC_CHECK_ 환경변수의 값을 1로 만들면 heap curruption이 발생했을 경우 진단 메시지가 화면에 출력되고, 2로 만들면 그 즉시 실행중이던 프로그램이 종료됩니다. 디버깅을 하다보면 heap curruption이 발생하는 시점과 프로그램이 SIGSEGV를 받는 시점이 달라서 디버깅하기 곤란할 때가 있는데, 그런 경우에 유용합니다. 적어도 '잘못된 일이 벌어지는 시점'과 '프로그램이 죽는 시점'을 똑같이 만들 수 있거든요. (printf에 의존적인 디버깅을 하시는 분들께는 이런 기법이 특히 유용하죠.) Solaris의 경우에는 watchmalloc을 사용하여 Linux와 비슷한 효과를 누릴 수 있습니다. 구글에서  man watchmalloc 해보시면 사용법을 아실 수 있으니까 설명은 생략하겠습니다. Linux와 사용법이 크게 다른 편은 아니랍니다.

하지만 뭐니뭐니 해도 메모리 관련 문제를 잡는 가장 좋은 방법은, 좋은 진단 툴을 사용하는 것입니다. 옛날부터 정평이 나 있는 메모리 누수 탐지 툴로는 purify같은 것이 있습니다만, 고가라 선듯 사용하기가 겁나죠. 하지만 Linux에서 프로그래밍을 하고 있다면, valgrind라는 막강한 툴이 있습니다.

valgrind는 -g 옵션을 주고 컴파일된 프로그램이라면 적용될 수 있습니다. 가령 컴파일된 실행파일의 이름이 a.out이라면, 다음과 같이 실행하면 됩니다.

valgrind --tool=memcheck ./a.out

--tool 옵션을 통해 실행 파일에 어떤 문제들이 내재되어 있는지를 살펴볼 수 있습니다. default는 memcheck이며, 메모리 관련 문제들을 검사하겠다는 뜻입니다. 메모리 누수(leak) 현상이 발생하는지의 여부 등을 이 옵션을 통해 검사할 수 있게 됩니다. 그것도 아주 빨리요.

프로그램(위의 경우에는 a.out)의 실행이 끝나면 valgrind는 다음과 같은 형식으로 탐지된 오류를 보고합니다.

==25832== Invalid read of size 4
==25832==    at 0x8048724: BandMatrix::ReSize(int, int, int) (bogon.cpp:45)
==25832==    by 0x80487AF: main (bogon.cpp:66)
==25832==  Address 0xBFFFF74C is not stack'd, malloc'd or free'd
위의 메시지에는 0xBFFFF74C에 대한 잘못된 메모리 참조가 발생했는데, 그 주소가 가리키는 메모리가 정상적으로 스택에 올라간 메모리도 아니고, malloc된 적도 없으며 free된 적도 없다는 것을 알리는 정보가 포함되어 있습니다. 참조가 발생한 위치도 요약되어 있구요.

최신의 Linux 배포판에는 이제 valgrind가 거의 번들되어 배포되고 있는 것 같습니다. 설사 설치되어 있지 않더라도, 요즘은 apt-get이나 yum 등 네트워크를 통해서 자동으로 프로그램을 설치할 수 있는 툴이 잘 정비되어 있으니까, 그 툴들을 사용하면 간단하게 설치해서 돌려볼 수 있습니다.

디버깅을 할 때 거의 아무런 도구를 사용하지 않는 프로그래머를 많이 볼 수 있습니다만, 메모리 관련 오류를 탐지하는 데 있어서는 이런 도구를 사용하는 쪽이 절대적으로 빠릅니다. 특히 오랜 시간 돌려놓으면 비주기적으로 죽어버리곤 하는 프로그램을 디버깅하는 데는 이런 도구를 활용하는 편이 낫죠.

valgrind에 대한 더 자세한 정보를 원하신다면 일단 여기로.


Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. valgrind.. 저는 memcheck 툴 사용보다 instruction instrumentation 용으로 잠깐 써 봤는데 좋더라고요.. 그런데 지금은 돌아가는 프로세스에 attach가 되나요?? 큰 프로그램에 memcheck같이 프로그램 살펴보는 tool을 붙이려고 하는데 attach가 안되서 처음부터 같이 돌려야 한다는.. 속도 죽음이죠 -_-;; (굼벵이) attach되면 좋겠는데..

    2008.02.17 14:08 [ ADDR : EDIT/ DEL : REPLY ]
    • 그러게요. 프로그램이 커질수록 그런 문제가 더 심각해지죠. 보통 메모리 검사를 시행하도록 해 놓으면 10배에서 20배 가까이는 느려진다고 하네요. Solaris에서 watchmalloc을 실행했을 때에는 정말 끔찍하게 느려지곤 했었죠. 10배 수준이 아니었던듯 ㅋ

      2008.02.17 14:47 [ ADDR : EDIT/ DEL ]

Extremely Agile/General2008. 2. 6. 12:46
오늘 이 글을 보다가 든 생각입니다만, 좋은 개발자가 되려면, 아무래도 인간성이 좋아야 할 것 같아요. (사실 이 비슷한 생각을 한 지는 오래 되었습니다.) 왜 이런 말을 하냐 하면, 적어도 국내에서는 다음의 두 가지 가정이 유효하기 때문입니다.

1. 개발자에게 주어지는 환경이 아주 척박하다
2. 혼자서 모든 개발을 다 해내는 것은 어려운 일이다.

우리나라의 SW 개발 환경이 외국에 비해 그다지 좋지 못하다는 이야기는 나온지 꽤 오래된 이야기입니다. 대부분의 개발 프로젝트는 일정 중심적(date-driven)인 프로젝트이고, 개발을 진행하다보면 요구사항(requirements)도 시도 때도 없이 변화합니다. 다른 프로그래머들과 맞춰야 하는 인터페이스는 한두가지가 아니고, 그러다보면 가끔 굉장히 비생산적인 논쟁에 빠지게 되는 일도 허다합니다.

그런데 이런 비-프로그래머 중심적인 환경에서도 빛나는 프로그래머들이 있습니다. Guru는 아닐지라도, 다른 사람의 가치를 내 가치처럼 중요하게 여기고, 내 편의를 위해 다른 사람의 불편함을 강요하지 않으며, 섣불리 다른 사람의 능력을 폄하하지 않는, 그런 프로그래머들 말입니다.

통계적으로 보면 (어디까지나 제 주관적인 통계에 의한 것이긴 합니다만) 그런 프로그래머들일 수록 신기술을 습득하는 데 주저하지 않고, 맘에 들지 않는 기술이라고 해서 자기 잣대로 폄하하지 않으며, 다른 사람들과의 관계를 원만히 이끌어 갑니다. 남들이 귀찮아하는 TDD도 솔선수범하는 경향이 있고, 문제가 생기면 언제나 자기가 맡은 부분을 주저없이 의심하는 자세를 보여주죠.

하지만 개발 환경이 척박해질 수록, 이런 개발자들도 함께 드물어진다는 것은 서글픈 일입니다. 이런 개발자들이 점차로 드물어진다는 것은, 소프트웨어 기술자들을 계속하여 재생산해 낼 책무를 지는 개발집단들이 개발자들에게 바람직한 역할 모델을 보여주지 못한다는 뜻이기도 합니다.

그런 역할 모델을 보여주지 못하는 이유로는 여러가지가 있겠습니다만, 가장 큰 것으로는 "개발 집단의 상당수가 개발 방법론을 잘 모른다"는 것을 꼽을 수 있겠습니다.

startup 회사부터 좀 잘 나가는 회사까지 여러 회사를 접해보았습니다만, 폭포수적인 개발방법론이나마 꾸준히 적용해보려는 의지를 가진 회사도 드물었고, 일의 양을 정확하게 추정해 보려는 시도를 하는 회사는 더더욱 드물었습니다. 잘못된 추정으로 시작하더라도 중간 중간에 그것을 바로잡을 기회는 자주 주어집니다만, 그 기회를 제대로 이용하는 회사도 드물었어요.

보통은 "야근-대책없는 휴식-야근-대책없는 휴식-야근-대책없는 휴식..."을 소프트웨어를 릴리즈하는 그날까지 반복하죠. 회의시간은 보통 "지시사항 전달"로 변질되고, 개발자들은 요구사항을 내놓은 사람들과 대면하고 요구사항을 이해할 기회조차 갖지 못합니다. 결국 잘 만들어놓으면 "그게 아니었어..."가 되니까 다시 야근을 하게 되구요. 이런 상황에서라면 아무리 인간성이 좋은 개발자라도 종국에는 인간성이 더러워지지 않을까요?

소프트웨어 개발도 남과 더불어 하는 일이니까, 결국 사람과의 인간관계가 가장 중요하기 마련인데, 그 관계를 차단한 다음 닭장에 밀어넣어 놓고는 "니가 할 일만 잘 하면 돼!"라고 하는 꼴이죠. 결국은 나중에 가서 "왜 내 말을 제대로 이해하지 못한거야!"라고 할거면서 말이에요. :-P

이런 억지스러운 환경을 개선하려면 어떻게 해야 할까요? 저는 요즘 그 답을 찾기 위해 애쓰는 중입니다.




Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 자네도

    개발자가 잘못된게 아니라 중간관리자 및 경영진이 잘못하고 있습니다.
    더 골(The goal)이라는 소설만 읽어도 이정도는 아니겠다는 생각이 듭니다.

    2008.02.08 12:57 [ ADDR : EDIT/ DEL : REPLY ]
    • 네. 개발자가 잘못되었다는 이야기는 한적이 없구요. ㅋㅋ 중간관리자 및 경영진을 포함하는 많은 사람들이 이런 상황을 개선하기 위해 공부를 좀 해야할것 같습니다.

      2008.02.08 17:31 [ ADDR : EDIT/ DEL ]

Extremely Agile/General2008. 1. 22. 05:55

SW 프로젝트는 일입니다. 모든 일이 다 그렇듯, 시간이 지나면 남은 일의 양은 차츰 감소하게 마련입니다. 하지만 대부분의 SW 프로젝트의 경우, 그렇지 않을 떄도 많습니다. 가장 흔한 것은, 마감 시간에 맞추어 밤을 새우는 일입니다. 일의 총량은 정해져 있을 터인데, 왜 프로젝트가 끝나갈 무렵까지 일의 양은 전혀 줄어들지 않고, 결국에는 철야를 하게 되는 것일까요?

애자일 방법론을 만든 사람들은 이 문제를 깊이 있게 연구했습니다. 그리고 그 결과, 이런 결론을 얻습니다.

일은 빵과 같다. 빵을 먹으면, 빵은 줄어든다. 일도 마찬가지이다. 일을 하면, 남은 일의 크기는 점점 작아진다. 하지만 '내가 먹으려는 이 빵이 정말로 내가 먹고 싶었던 빵인지' 한 입 베어물어 보기 전에는 정확하게 알 수 없는 것과 마찬가지로, 고객에게 소프트웨어를 인도하기 전까지는 누구도 그것이 정말로 고객이 원했던 그것인지 확신할 수가 없다.

고객이 원하지 않는 기능을 구현하게 되면, 그 기능을 구현하는 데 투자했던 시간들이 고스란히 오버헤드가 되고, 결국 고객이 정말로 원하는 기능을 구현하기 위해 프로젝트 후반부에 날밤을 새우게 됩니다. 이런 문제에 대해서는 마이크 콘(Mike Cohn)을 비롯한 여러 사람들이 많은 연구를 했고, 좋은 책도 많이 있습니다. Agile Estimating and Planning은 그 중 하나입니다.

하지만 오늘은 그 보다는 좀 더 개인적인 이야기를 해 볼까 합니다. 개인적인 견지에서 보면, 프로젝트 막바지에 더 바쁜 가장 결정적인 이유는, 프로젝트 중간 중간에 박혀있는 마일스톤 점검 시점에 반드시 해야 할 몇 가지 일들을 하지 않고 넘어갔기 때문입니다.

마일스톤 점검시에, 제가 속한 팀은 주로 그 때 까지 개발한 소프트웨어의 큰 틀이 정상동작하는지를 점검합니다. 속칭 '연동시험'을 하는 것이죠. 이 연동시험은 개발에 참여하는 여러 업체와 개발자들로 하여금 '다른 사람의 관점에서' 시스템을 바라볼 수 있도록 해 주기 때문에 굉장히 중요합니다. 그 과정에서 '개발에는 별로 적합하지 않은' 업체나 개발자가 가려지기도 하죠. ('다른 사람의 입장을 전혀 고려하지 않는 개발자'는 그 한 예가 되겠습니다. 이런 개발자일수록 '개발자의 자질'에 대해 더 많이 이야기하곤 한다는 것은 아이러니 한 일이죠. 좋은 코더가 되는 것도 중요하지만, 사실 더 중요한 것은 존중의 자세입니다.)

그런데 이 마일스톤 점검시에는 보통 자질구레한 사항이나 버그들에 신경을 쓰기 보다는, 서로 다른 사람들이 만든 시스템이 별 탈 없이 연동되어 돌아가는지를 살펴보는데 집중하는 일이 많습니다. 자잘한 문제들은 TO-DO 리스트에 적어놓는 것으로 점검을 마무리 짓곤 하죠.

문제는 그렇게 만들어진 TO-DO 리스트를 나중에는 아무도 거들떠보지 않는다는 사실입니다. 그러다보니, 데드라인이 다가오면 최종 연동시험과 병행해서 '자질구레한' 버그들도 함께 잡아 나가야 하는 일이 벌어지게 되고, 결국은 잠을 줄이게 되는 것이죠.

'엉뚱한 기능'을 구현하는 것도 위험천만한 일이지만, 진짜 '일'을 남겨두는 것도 위험천만하기는 마찬가지라는 거에요. 그런데 이런 실수를 되풀이하게 되는 이유는 대체 뭘까요?

가장 큰 이유는, 그런 '뒷마무리 작업'이 대체로 재미가 없기 때문입니다. 뭔가 그럴듯한 새 기능을 시스템에 추가하는 건 재미있습니다만, 그 부산물들을 치우는 것은 사실 지루한 일이죠. 물론, 그런 뒷마무리 작업을 충실히 할 만큼 넉넉한 시간이 주어지지 않는 경우도 있습니다. 자고 일어나면 세상은 달라져 있고 또 뭔가 새로운 요구사항은 생겨나게 마련이니까요.


PS.

이 글은 새벽 네시쯤 쓴 것 같군요.
저도 날밤을 새고 있었다는 이야기죠. ㅋㄷ



 

Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. SW개발은 아니지만 나름 개발일을 하고 있는 완전공감이에요 ;ㅁ;
    내일까지 프로젝트마감을 맞추기위해 1주일전부터 완전철야중. 하루에 2~3시간밖에 못자는것 같아요 ;ㅁ;
    지금도 밤샘중 흑흑흑;;;
    왜 마지막날이 되면 온갖 수정사항들이 나오는건지 TO DO LIST가 줄질않네요.
    한개 지우면 2~3개 추가되고. orz

    2008.01.22 06:27 [ ADDR : EDIT/ DEL : REPLY ]
    • 그러게요. 이런 문제를 극복해 나가는 현명한 방법을 찾는 것이 개발자들이 해야 할 일이겠죠. 개인적으로는 TO-DO LIST를 관리하는 것 보다는, TO-DO BOARD(해야할 일 게시판)을 만들어 모두가 볼 수 있는 자리에 배치하는 것도 한 방법이 되지 않을까 생각하고 있습니다. 그러면 시간이 지날수록 그 게시판에 나열된 개선 항목들이 줄어드는 모습을 볼 수 있어서 좋을 거라고 생각해요. 어쩐지 '사용자 스토리'의 동어반복같다는 생각도 드는군요. 덧글 감사합니다.

      2008.01.22 06:44 [ ADDR : EDIT/ DEL ]
  2. 개발자만 공감하는 얘기는 아니랍니다 ;ㅁ;
    기획자도 똑같은 짓(?)을 하고 있다죠

    제 시간관리 능력을 탓해야 겠지요 쩝

    2008.01.22 07:47 [ ADDR : EDIT/ DEL : REPLY ]
    • 기획자도 개발팀의 일원이니 개발자라고 보는 것이 좋겠죠. 시간관리라는 문제를 개인 차원의 문제로 접근하면 전반적인 상황은 크게 개선되지 않을 수도 있다고 생각합니다. 가급적 팀 단위의 해결책을 찾는 것이 좋겠죠.

      2008.01.22 09:25 [ ADDR : EDIT/ DEL ]
  3. 아직 프로젝트에 대한 경험이 없어서 선배분들의 얘기가 마냥 멀리 느껴집니다.OTL.....
    하지만 토이박스를 몇 번 만들어본터라 막바지에 자질구레한 일 때문에 지겹고 바빠진다는 것을 매번 느꼈습니다.;;ㅜㅜ

    2008.01.22 08:54 [ ADDR : EDIT/ DEL : REPLY ]
    • 그런 일들이 쌓이고 쌓이면 결국 개발이라는 것이 막판에는 재미없는 일이 되어버리겠죠.

      2008.01.22 09:26 [ ADDR : EDIT/ DEL ]
  4. 저는 프로그램 개발과는 완전 거리가 먼....출판 편집을 하고 있지만 와닿네요. ㅡㅡ;;

    2008.01.22 22:19 [ ADDR : EDIT/ DEL : REPLY ]
  5. ㅡㅡ;

    [펌] 해가겠습니다. ^^; 당연히 펌한 url 과 명시는 꼭 하겠습니다. ^^;
    감사합니다. 펌한 사이트 : http://cafe.daum.net/aspdotnet

    2008.02.13 00:36 [ ADDR : EDIT/ DEL : REPLY ]