반응형
사용자 삽입 이미지

사용자 삽입 이미지

Posted by 1010
반응형
1. 스프링 프레임워크 소개

  * 스프링이란?
    - 스프링(Spring)은 간단히 말하면 엔터프라이즈 어플리케이션에서 필요로 하는 기능을 제공
      하는 프레임워크이다. 스프링은 J2EE가 제공하는 다수의 기능을 지원하고 있기 때문에,
      J2EE를 대체하는 프레임워크로 자리 잡고 있다.

  * 스프링 프레임워크 특징
    - 스프링은 경량 컨테이너이다. 스프링은 자바 객체를 담고 있는 컨테이너이다. 스프링은 이들
      자바 객체의 생성, 소멸과 같은 라이프 사이클을 관리하며, 스프링으로부터 필요한 객체를
      가져와 사용 할 수 있다.
    - 스프링은 DI(Dependency Injection) 패턴을 지원한다. 스프링은 설정 파일을 통해서 객체 간의
      의존 관계를 설정할 수 있도록 하고 있다. 따라서 객체는 직접 의존하고 있는 객체를 생성 하거
      나 검색할 필요가 없다.
    - 스프링은 AOP(Aspect Oriented Programming)를 지원한다.스프링은 자체적으로 AOP를 지원
      하고 있기 때문에 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통으로 필요로 하지만 실제
      모듈의 핵심은 아닌 기능들을 분리해서 각 모듈에 적용할 수 있습니다.
    - 스프링은 POJO(Plain Old Java Object)를 지원한다. 스프링 컨테이너에 저장되는 자바 객체
      는 특정한 인터페이스를 구현하거나 클래스를 상속받지 않아도 된다. 따라서 기존에 작성한
      코드를 수정할 필요 없이 스프링에서 사용할 수 있다.
    - 트랙젝션 처리를 위한 일관된 방법을 제공한다. JDBC를 사용하든, JTA를 사용하든, 또는 컨테
      이너가 제공하는 트랙잭션을 사용하든, 설정 파일을 통해 트랜잭션 관련 정보를 입력 하기
      때문에 트랙잭션 구현에 상관없이 동일한 코드를 여러 환경에서 사용 할 수 있다.
    - 영속성과 관련된 다양한 API를 지원한다. 스프링은 JDBC를 비롯하여 iBATIS, Hibernate,
      JPA, JDO등 데이터베이스 처리와 관련하여 널리 사용되는 라이브러리와의 연동을 지원하고
      있다.
    - 다양한 API에 대한 연동을 지원한다. 스프링은 JMS, 메일, 스케쥴링 등 엔터프라이즈 어플리
      케이션을 개발하는데 필요한 다양한 API를 설정 파일을 통해서 손쉽게 사용할 수 있도록 하고
      있다.

  * IoC(Inversion of Control)란?
    - Spring 프레임워크가 가지는 가장 핵심적인 기능은 IoC(Inversion of Control)이다.자바가
      등장한 최초에는 객체 생성 및 의존관계에 대한 모든 제어권이 개발자에 있었다. 그러나,
      서블릿, EJB가 등장하면서 제어권이 서블릿과 EJB를 관리하는 서블릿컨테이너 및 EJB 컨테이
      너에게 넘어가게 되었다. Spring 프레임워크도 객체에 대한 생성 및 생명주기를 관리할 수
      있는 기능을 제공하고 있다. 이와 같은 이유때문에 Spring 프레임워크를 Spring  컨테이너,
      IoC컨테이너와 같은 용어로 부르기도 한다.
     : 물론 모든 객체에 대한 제어권을 컨테이너에게 넘겨버린 것은 아니다. 서블릿 컨테이너와 EJB
       컨테이너에서도 서블릿과 EJB에 대한 제어권만 컨테이너가  담당하고 나머지 객체에 대한 제
       어권은 개발자들이 직접 담당하고 있다. 이처럼 Spring 컨테이너 일부 POJO(Plain Old Java
       Object)에 대한 제어권을 가진다. Spring컨테이너에서 관리되는 POJO는 각 계층의 인터페이
       스를  담당하는 클래스들에 대한 제어권을 가지는 것이 대부분이다.

  * Dependency Injection
    - DI는 Spring 프레임워크에서 지원하는 IoC의 한 형태이다. DI를 간단히 말하면, 객체 사이의
      의존관계를 객체 자신이 아닌 외부의 조립기(assembler)가  수행한다는 개념이다.

      일반적인 웹어플리케이션의 경우 클라이언트의 요청을 받아주는 컨트롤러 객체, 비지니스
     로직을 수행하는 서비스 객체, 데이터에 접근을 수행하는 DAO객체 등으로 구성된다.
     만약, WriteArticleServiceImple클래스가 ArticleDao 인터페이스에  의존하고 있을 경우를 생각
     해보자

     의존관계를 형성하는 방법에는 아래와 같은 경우들이 있다.

     첫번째,  코드에 직접 의존 객체를 명시하는 경우.
   
     public class WriteArticleServiceImpl{

        private Article articleDao = new MysqlArticleDao();

     ....

     }

     이 경우 의존하는 클래스가 변경되는 경우 코드를 변경해야 하는 문제가 있다.

     두번째, Factory 패턴이나 JNDI등을 사용해서 의존 클래스를 검색하는 방법이 있다.

     public class WriteArticleServiceImpl{

        private ArticleDao articleDao = ArticleDaoFactory.create();

        ...

     }

     Factory나 JNDI를 사용하면 첫번째문제(즉,  의존  클래스가 변경되면 코드를 변경해야 하는
     문제)를 없앨 수는 있지만, WriteArticleServiceImpl클래스를  테스트하려면 올바르게 동작하는
     Factory또는 JNDI에 등록된 객체를 필요로 한다는 문제점이 있다.

     세번째, DI패턴이다. 이 방식에서는 의존관계에 있는 객체가 아닌 외부 조립기(assembler)가 
     각 객체 사이의 의존 관계를 설정해준다.
     WriteArticleServiceImpl클래스의  코드는 MysqlArticleDao 객체를 생성하거나 검색하기 위한
     코드가  포함되어  있지 않다. 대신 조립기의 역할을 하는 Assembler가 MysqlArticleDao
     객체를 생성한 뒤 WriteArticleServiceImpl객체에 전달해 주게  된다.
 
     DI패턴을 적용할 경우 WriteArticleServiceImpl클래스는 의존하는 객체를  전달받기  위한 설정
     메서드(setter method)나 생성자를  제공할 뿐 WriteArticleServiceImpl에서  직접 의존하는
     클래스를 찾지 않는다.

  * AOP(Aspect Oriented Programming)
    - 로깅, 트랙잭션처리, 보안과  같은 핵심 로직이  아닌 cross-cutting concern(공통관심사항)을
      객체 지향기법(상속이나 패턴등)을 사용해서 여러 모듈에 효과적으로 적용하는데 한계가 있었
      으며, 이런 한계를 극복하기 위해 AOP라는 기법이 소개되었다.

      공통관심사항(cross-cutting concern)을  별도의 모듈로 구현한 뒤, 각  기능을 필요로 하는 곳
      에서 사용하게 될 경우, 각 모듈과 공통 모듈 사이의 의존관계는 복잡한 의존 관계를 맺게 된다.

      AOP에서는  각클래스에서 공통 관심사항을 구현한 모듈에 대한  의존  관계를갖기보다는,
      Aspect를  이용하여 핵심 로직을 구현한 각 클래스에 공통 기능을 적용하게 된다.

      AOP에서는 핵심  로직을 구현한 클래스를 실행하기 전 후에 Aspect를 적용하고, 그 결과로
      핵심 로직을 수행하면 그에 앞서 공통 모듈을 실행하거나 또는 로직  수행 이후에 공통 모듈을 
      수행하는 방식으로 공통 모듈을 적용하게 된다.

      AOP에서 중요한 점은 Aspect가 핵심 로직 구현 클래스에 의존하지 않는다는 점이다. AOP에
      서는 설정 파일이나  설정클래스  등을 이용하여 Aspect를 여러 클래스에  적용할 수 있도록
      하고 있다.
Posted by 1010
반응형

스프링 공부를  하는데.. 아무리 이론을 봐도 무슨 말인지 하나도 모르겠고,
자바 처음 배우던 때를 떠올리며.. Helloworld를 찍어봐야겠다고 판단.
네이버에서 찾아 낸 Spring으로 Helloworld찍기.. 시킨대로 하니 화면에 뜨네. 휴.
자 그럼, 안 잊어 먹기 위해 나도 정리를 해놓자.

내 컴퓨터 환경은...
JDK1.5 + Tomcat 5.5 + SpringFramework 2.5.6 아 차,, +  Eclipse는 Europa.

우선 스프링 프레임워크를 받으려면..
http://www.springframework.org/download 로 가서
spring-framework-2.5.6-with-dependencies  파일을 받는다.
뭘 받을지 모르겠음 멜 남기시고..

자 그럼 Eclipse 실행 시키고~ Dynamic Web Project를 만든다.
프로젝트 이름은 SpringHelloWorld 라고 지정.
Target Runtime은 Tomcat으로 하고 Finish.

lib폴더에 lib파일들을  넣는다.(제일 중요!!)
스프링 다운 받아서 압축 푼 폴더에 보면
spring-framework-2.5.6\dist\spring.jar 파일
: (스프링  프레임워크 lib파일)
spring-framework-2.5.6\lib\jakarta-commons\commons-logging.jar 파일
: (로그 사용을 위한 lib파일)
spring-framework-2.5.6\dist\modules\spring-webmvc.jar
: (컨트롤러 상속받기 위해서)

그리고 태그라이브러리 사용을 위한 jar파일을 받는다.
http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi
압축풀어 보면 standard.jarjstl.jar파일이있다. 

이 아이들까지전부 lib폴더에넣는다..

그럼 web.xml파일부터 수정해보자.

 <display-name>SpringHelloWorld</display-name>
   <servlet>
     <servlet-name>springapp</servlet-name>
     <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
     </servlet-class>
   </servlet>
 
  <servlet-mapping>
    <servlet-name>springapp</servlet-name>
    <url-pattern>*.htm</url-pattern>
 </servlet-mapping>
 <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
</web-app>

*.htm으로 들어오는 요청에 대해서 springapp서블릿을 사용하겠다는 얘기.
서블릿은 스프링 프레임워크에 있는 DispatcherServlet 이다.

DispatcherServlet의  제어 행위를 처리하는 설정 파일이 필요한데, 이것은 서블릿 이름에
-servlet.xml 을 붙여 작성한다. 그러니까 여기서는 springapp-servlet.xml  이겠지.
/WEB-INF/springapp-servlet.xml을  작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
 "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <bean id="springappController" class="web.SpringappController"/>
 
    <bean id="urlMapping"
     class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
     <property name="mappings">
      <props>
       <prop key="/hello.htm">springappController</prop>
      </props>
     </property>
    </bean>
 
    <bean id="viewResolver"
     class="org.springframework.web.servlet.view.InternalResourceViewResolver">
     <property name="viewClass">
      <value>org.springframework.web.servlet.view.JstlView</value>
     </property>
     <property name="prefix">
      <value>/WEB-INF/jsp/</value>
     </property>
     <property name="suffix">
      <value>.jsp</value>
     </property>
    </bean>
</beans>

잘 보면 beans태그안에 bean태그가  3개가있다.
첫 번째는 Controller인거 같고,,
두 번째는 urlMapping 시켜주는데 /hello.htm으로 요청이 들어오면 springappController에게
넘기라는 거 같다.
세 번째꺼는 선행자 후위자를 정한거네.
미리  써놓으면 항상 쓰이는 WEB-INF/jsp/파일이름.jsp 에서 앞 뒤 없이 이름만 써서 가능.

그럼 SpringappController.java를 작성해 보자.
이놈은 Controller라는  인터페이스를 상속받습니다.
web이라는 패키지를 생성 후 거기에다가 class를 생성합니다.
생성할 때 New Java Class화면에서 Interface부분에 Add버튼을 클릭해서
Controller를선택하고 추가 해주면 자동으로 생성됨.

package web;

import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class SpringappController implements Controller {
 protected final Log logger = LogFactory.getLog(getClass());
 
 public ModelAndView handleRequest(HttpServletRequest arg0,
   HttpServletResponse arg1) throws Exception {
  // TODO Auto-generated method stub
  String now = (new Date()).toString();
  logger.info("returning hello view with " + now);
 
  return new ModelAndView("hello", "now", now);
 }
}

시간을 리턴한다. new Date()로 시간을 생성해서 그걸 String변환 후 now 변수에  넣고,
로그를 기록한 후, return할때 now라는 변수는 now라는 이름으로 쓸 수 있게 hello파일에 넘김.
이  hello는 앞  뒤  합쳐서 /WEB-INF/hello.jsp 가 됨.

이제 /WEB-INF/jsp/hello.jsp 파일을  보자.

<%@ page language="java" contentType="text/html; charset=windows-31j"
    pageEncoding="windows-31j"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-31j">
<title>Hello :: Spring Application</title>
</head>
<body>
 <h1>Hello World! - Spring Application</h1>
 <p>Greetings, it is now <c:out value="${now}" /></p>
</body>
</html>

now라는 변수를 c:out태그로 출력한다. Controller에서시간을담아서 넘겨 받았다.
그리고 jsp파일을 WEB-INF폴더에 넣는 이유는 JSP파일접근에대한권한이 아마 우리한테 없고 Spring에게 넘겨서라고 내가 찾아 본 블로그 주인장은 말했다..
즉, 직접적으로 접근하지 못하도록 하려고?

was/SpringHelloWorld로 접속시 index.jsp를 실행하는데 파일이 없으니  하나 만들자.
폴더 위치 : WebContent/index.jsp
<head>와  </head> 사이에
<script>
 location.href="hello.htm"
</script>
이렇게만 입력하면 자동으로 hello.htm으로 포워드 해준다.
그럼 실행!

사용자 삽입 이미지

이상.. Helloworld찍을 수 있게 도움 받은 블로그 Kyle's Home님께 감사드린다.
이 글의 내용도 Kyle's Home님의 글을 거의 그대로 옮겨 놓았다.
Posted by 1010
98..Etc/Tomcat2008. 8. 17. 02:48
반응형

1. jdk 1.5이상이면 아래 설정을 JAVA_OPTS안에 추가한다면 YourKit을 가지고 힙덤프를 분석할 수 있다.
 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/heapdump


 

2. Jasper 2 JSP Engine 지원을 위해 $CATALINA_BASE/conf/web.xml 설정 변경하라.
 - development : false, genStringAsCharArray : true, modificationTestInterval : true, trimSpaces : true
 - 자세한 건 여기를 참조하라.


3. 가용성 확보를 위해 Tomcat의 clustering/session replication을 사용하라.
 - 사용방법은 여기를 참조하라.


4. error pages를 작성하여 적용하라.(web.xml)
 - <error-page>
      <error-code>404</error-code>
      <location>/error/404.html</location>
   </error-page>


5. 어플리케이션에서 System.out과 System.err를 제거하고 Log4j를 사용하라.


6. application마다 같은 라이브러리는 WEB-INF/lib에서 CATALINA_HOME/shared/lib로 옮겨서 공유하라.
 - 메모리를 절약할 수 있다.


7. memory parameters를 잘 활용하라.


8. 불필요한 어플리케이션을 제거하라.


9. Manager서버의 보안을 강화하라.
 - CATALINA_HOME/conf/tomcat-users.xml
  <role rolename="manager">
   <user username="darren" password="ReallyComplexPassword" roles="manager"></user>
  </role>
 - CATALINA_HOME/conf/server.xml에 IP 블럭킹 기능도 유용하다.
  <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="192.168.1.*"></Valve>


10. server.xml을 최적화 하라.
 - 서버 환경에 맞는 CATALINA_HOME/conf/server-original.xml, CATALINA_HOME/conf/server-minimal.xml 선택하여 server.xml로 변경하라.


11. 톰켓 업그레이드는 설치디렉토리를 분리하여 적용하라.


12. Tomcat 서버는 root로 띄우지 마라.
 - 자세한 내용은 여기를 참조하라.


13. Precompile JSPs (at build time)
 - 자세한 내용은 여기를 참조하라.


14. 디렉토리 보이는 걸 막아라.
  - CATALINA_HOME/conf/web.xml
  <servlet>
   <servlet-name>default</servlet-name>
   <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
   <init-param>
   <param-name>debug</param-name>
   <param-value>0</param-value>
   </init-param>
   <init-param>
   <param-name>listings</param-name>
   <param-value>false</param-value>  <!-- make sure this is false -->
   </init-param>
   <load-on-startup>1</load-on-startup>
  </servlet>


15. 듀얼 코어 CPU를 사용할 경우 쓰레드풀을 250개 이상 설정하라.


16. Tomcat MBeans이나 다양한 모니터링 도구를 활용하여 서버를 모니터링하라.
 - 자세한 내용은 여기를 참조하라.


17. jdk1.5, 1.6이 성능이 좋다.


18. -server JVM option을 사용하라.


19. GZIP compression을 사용하라.
 - <Connector>compression="on"
   compressableMimeType="text/html,text/xml,text/plain,application/xml"
   </Connector>
 - 자세한 건 여기를 참조하라.


20. Security Manager를 잘 사용하라.
 - 자세한건 여기를 참조하라.



출처 : http://blog.paran.com/pmang/21456729
Posted by 1010
90.개발관련문서2008. 8. 17. 02:40
반응형
사용자 삽입 이미지
새로 보는 프로그래밍 언어
개발자의 가치를 높이는 프로그래밍 언어 대백과
마이클 스콧 지음 | 민병호 김진혁 옮김 | 1004페이지 (부록CD 260페이지 추가수록)
45,000원 | 2008년 8월 21일 출간 예정

이 책을 한 마디로 표현해볼까요?
괴물!
프로그래밍 언어를 다룬 책 중에 여태까지 이런 책은 한 권도 없었습니다.
여기서 "이런"은 뭘 의미할까요? eRun? 자, 이제부터 차근차근 하나씩 설명해드리죠.

정말 몸값을 높이고 싶으신가요? 단지 코드 몇 줄을 잘 짠다고 몸값이 올라간다고 생각하시나요? 개발자로서 자신의 앎이 부족하다고 느끼신 적은 없으신가요? 학교에서 배우는 프로그래밍 언어론이 수박 겉핥기에 지나지 않는다고 느껴보신 적은 없습니까.

이 책의 원서 목차를 보고 난다긴다 하는 개발자분들이 모두 혀를 내두르셨습니다. 심지어 어떤 프로그래밍 언어를 전공하셨다는 일급 자바 개발자께선 "이런 '발칙한 책'을 봤나"라는 반응을 하실 정도였으니까요. (사실은 좀더 저급한 표현을 쓰셨지만, 여기서는 조금 정제해서 말씀드려야겠죠 -0-)
사용자 삽입 이미지
이 책에서 다루고 있는 프로그래밍 언어의 "간추린" 계보입니다. 이 책에서는 명령형 언어, 함수형 언어, 논리형 언어, 병행/병렬 프로그래밍, 그리고 최근 부쩍 떠오르는 스크립팅 언어까지 거의 "모든" 언어를 다루고 있습니다. 그저 다루는 정도가 아니라 각 언어로 구현한 코드 사례를 보여주며 그 언어의 장단점까지를 파헤치고 있습니다. 개발자가 빠지기 쉬운 함정과 오류를 낱낱이 보여줍니다.
사용자 삽입 이미지
그림이 좀 보이세요? 이 책에서 다루는 간단 목차입니다. 네, 이 책의 당초 집필 목적은 교과서입니다. 학부 3,4학년, 혹은 대학원생을 위한 아주아주 훌륭한 교과서입니다. 잠깐만요! 그렇다고 단지 교과서로 지루하게 나열된 책은 결코 아닙니다. 기존 프로그래밍 언어론 책에서는 다루지 않았던 정석과 기본기를 차근차근 쌓아가게 하는 책입니다.

1부. 프로그래밍 언어의 기초에서는 컴파일러와 컴퓨터 아키텍처, 프로그래밍 언어 설계와 구현에 대한 기본기를 닦아줍니다. 2부. 프로그래밍 언어의 핵심요소에서는 명령형 언어 C와 C++등을 중심으로 프로그래밍 언어의 공통적인 사항과 기본 개념을 다룹니다. 이어지는 3부. 다른 관점에서 바라본 프로그래밍 모델에서는 함수형 언어, 논리형 언어, 병행 프로그래밍 등과 함께 이 책의 (원서) 개정판에서 추가된 내용으로 PHP, 루비, 파이썬 등 스크립팅 언어들까지 다루며 내용을 이어갑니다. 4부에서는 드디어 로우레벨 관점에서 바라본 프로그램 되짚기가 등장합니다. 중간언어 등을 훑어보며, 프로그램을 컴파일 후 기계어로 변환된 코드를 분석해 코드를 개선하는 프로그램 최적화까지 다루게 됩니다.

그야말로 프로그래밍 언어의 개념, 이론, 설계, 구현, 최적화 등 전체적인 프로그래밍 언어의 전반을 꿰뚫고 각 사례를 모든 프로그래밍 언어로 구현해 그 언어들의 강점과 취약점을 분석해줌으로써, 결국 개발자가 프로그래밍 과정에서 가장 적절한 언어를 어떻게 선택해내 얼마나 효율적인 프로그램을 만들 수 있을까를 알려주는 것을 최종 목표로 합니다.

이제 왜 책의 제목에 "새로 보는"이라는 수식어가 붙었는지 이해가 가세요? 저희도 처음엔 아주 쉽게 "완전정복"이라든가 "완벽 가이드" "총서" 등의 아주 식상한 문구를 생각했었습니다. 하지만 이 책은 기존에는 전혀 볼 수 없었던 프로그래밍 언어론에 대한 절대(!)적이고도 상식(!)적인 프로그래밍 언어 대백과라고 할 수 있습니다. 기존 관념을 깨고 혜안을 갖게 해주는, 프로그래밍 언어의 비밀을 한꺼풀 벗겨내는 책이거든요. 자, 이 정도면 왜 자신있게 여러분의 몸값을 높여주는 책이라고 큰 소리를 쳤는지 조금은 동의를 하시나요? 아직도 이해를 못 하시겠다면 8월 21일 서점에 가셔서 직접 확인하시기 바랍니다. :)

게다가 함께 제공하는 부록CD에는 책의 분량(이미 1004페이지, 천사에요. angel ^^)이 비대해지는 것을 막기 위해 중간중간 좀더 심도깊은 심화학습에 해당하는 내용을 옮겨 담았습니다. 260페이지에 달하는 분량인데요. 이것만 해도 웬만한 책 한 권 분량은 족히 됩니다. 부록 CD에는 그밖에도 공개 컴파일러와 해석기 링크 모음, 책에 나온 300개 이상의 코드의 완전한 소스파일 등이 가득가득 들어있습니다. 지금 CD 메뉴 내비게이션까지 한글로 옮겨내는 마무리 작업 중인데요. 독자를 감동시킬 만한 세심한 작업에 최선을 다하고 있습니다. 원서보다 나은 번역서, 에이콘 책이 늘 지향하는 바죠.

이 책을 번역한 역자 민병호님과 김진혁님은 강유님과 함께 『TCP/IP 완벽 가이드』를 공역하셨던 분들인데 드디어 이제 두 분만의 멋진 작품을 만들어내셨습니다. 그때만해도 대학원생 신분이셨는데 이제 한 분은 국방과학연구소에서 한 분은 삼성전자에서 열혈 근무중인 사회인이 되셨어요. 공교롭게도 이 분들의 번역 작품 두 권이 모두 1000페이지가 넘어가는 방대한 작업이었습니다. TCP/IP 완벽 가이드가 1,600쪽이었고, 이 책 『새로 보는 프로그래밍 언어』는 CD에 담긴 내용까지 합치면 1,260쪽을 넘어서니 정말 어마어마하죠. 그간 정말 고생 많으셨어요. TCP/IP 책을 마치고 출판사를 찾아오셔서 "한우 꽃등심 사주세요"하셨었는데, 이번에 책이 나오면 뭔가 미리 준비를 해둬야 할 것 같습니다. ㅎㅎ

제조물 책임법(Product Liability Law) 일명 PL법이라는 게 있습니다. "고양이를 렌지에 넣고 돌려 털을 말리지 마세요"(전자렌지)라든가 "에어콘에서 나오는 물은 마시지 마세요"(에어콘) 등 기상천외한 경고문을 삽입해둠으로써 소비자가 제품 오용으로 인한 피해를 막기 위하고 불가피한 소송을 피하기 위함이라고 하는데요. 정말 기상천외하고 황당한 경고문이 많습니다. 세상은 요지경이잖아요. 별의별 일이 다 일어나는 별천지 세상.

저희도 판권지쪽에 문구하나를 삽입해야 할까요?

"누워서 이 책을 읽으시다가 책을 놓치시면 머리에 심각한 상해를 입을 수 있습니다"
(대처법: 반드시 엎드려서 읽으세요!)
"베고 주무시다가 목 부분에 경련이 일어나 며칠간 고생을 할 수 있습니다"
(대처법: 좀더 얇은 다른 책을 고르세요.)
"책을 읽고 났는데도 가치가 높아지지 않았다구 느끼신다구요? 제대로 꼼꼼히 읽으셨는지 다시 한번 확인해보세요. 그럴 리가 없습니다. :) "


모두 열공하셔서 정말 훌륭한 개발자 되세요! 고고싱!!
Posted by 1010
98..Etc/Anyframe2008. 8. 17. 02:32
반응형
사용자 삽입 이미지
Posted by 1010
98..Etc/Anyframe2008. 8. 17. 02:27
반응형
사용자 삽입 이미지
Posted by 1010
98..Etc/Anyframe2008. 8. 17. 01:23
반응형
  • Apache Ant 설치와 사용 (1.1 버전 기반)
  • Apache Ant는 Java기반의 빌드 도구이다.
  • Ant의 주요 기능

자바 소스파일 컴파일
jar. war. ear. zip파일 생성
javadoc을 실행하여 도움말 생성
파일이나 폴더의 이동 및 복사, 삭제
각각의 작업에 대한 의존성 설정
유닉스에서 처럼 파일이나 폴더에 퍼미션 설정
파일의 변경 날짜를 설정하는 touch 기능
외부 프로그램의 실행

  • Ant의 설치

다운로드 사이트 : http://ant.apache.org
zip파일을 적당한 디렉토리에 압축을 해제한다.
bin 디렉토리를 path에 추가합니다.
환경 변수 ANT_HOME을 Ant 인스톨된 장소의 디렉토리로 설정합니다.
추가로, 환경 변수 JAVA_HOME 을 설정합니다. 이것에는 JDK가 인스톨 된 장소의 디렉토리로 설정합니다.

  • 시스템 요구사항

Ant 는, Linux, Solaris 나 HP-UX 라고 하는 상용 Unix, Windows 9x 및 NT, Novell Netware 6 , 그리고 MacOS X 등을 포함한 수많은 플랫폼에서 잘 사용되고 있습니다. Ant를 빌드해 사용하려면, 클래스 패스에 JAXP 호환의 XML 파서가 인스톨되어 이용 가능해야 합니다.

  • PATH 설정하기

Windows OS :

      설치된 디렉토리가 c:\ant\. 로 가정했을 경우에

     set ANT_HOME=c:\ant

     set JAVA_HOME=c:\jdk1.3.1

     set PATH=%PATH%;%ANT_HOME%\bin

Unix(bash) OS :

    export ANT_HOME=/usr/local/ant

    export JAVA_HOME=/usr/local/jdk1.3.1

    export PATH=${PATH}:${ANT_HOME}/bin

---------------------------------------------------------------------------------

FreeTTS Client/Server Demo 분석 중 실행에 필요한 Ant를 설치하느라고 네이버 블로거님들이 퍼나르던 좋은 정보를 필요한 부분만 발췌하였습니다.

Posted by 1010
90.개발관련문서2008. 8. 14. 10:38
반응형
How to be a Programmer: A Short, Comprehensive, and Personal Summary

프로그래머가 되는 방법: 짧고 폭넓고 개인적인 요약.



Copyright © 2002, 2003 Robert L. Read

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with one Invariant Section being 'History (As of May, 2003)', no Front-Cover Texts, and one Back-Cover Text: 'The original version of this document was written by Robert L. Read without renumeration and dedicated to the programmers of Hire.com.' A copy of the license is included in the section entitled 'GNU Free Documentation License'.

목차

Contents

[-]
1 도입
2 초보자
2.1 개인적 기능들
2.1.1 디버그 배우기
2.1.2 문제 공간을 나눠서 디버그 하는 방법
2.1.3 오류를 제거하는 방법
2.1.4 로그를 이용해서 디버그 하는 방법
2.1.5 성능 문제를 이해하는 방법
2.1.6 성능 문제를 해결하는 방법
2.1.7 반복문을 최적화하는 방법
2.1.8 I/O 비용을 다루는 방법
2.1.9 메모리를 관리하는 방법
2.1.10 가끔씩 생기는 버그를 다루는 방법
2.1.11 설계 기능을 익히는 방법
2.1.12 실험을 수행하는 방법
2.2 팀의 기능들
2.2.1 시간 추정이 중요한 이유
2.2.2 프로그래밍 시간을 추정하는 방법
2.2.3 정보를 찾는 방법
2.2.4 사람들을 정보의 원천으로 활용하는 방법
2.2.5 현명하게 문서화하는 방법
2.2.6 형편없는 코드를 가지고 작업하기
2.2.7 소스 코드 제어 시스템을 이용하는 방법
2.2.8 단위별 검사를 하는 방법
2.2.9 막힐 때는 잠깐 쉬어라
2.2.10 집에 갈 시간을 인지하는 방법
2.2.11 까다로운 사람들과 상대하는 방법
3 중급자
3.1 개인적 기능들
3.1.1 의욕을 계속 유지하는 방법
3.1.2 널리 신뢰받는 방법
3.1.3 시간과 공간 사이에서 균형을 잡는 방법
3.1.4 압박 검사를 하는 방법
3.1.5 간결성과 추상성의 균형을 잡는 방법
3.1.6 새로운 기능을 배우는 방법
3.1.7 타자 연습
3.1.8 통합 검사를 하는 방법
3.1.9 의사소통을 위한 용어들
3.2 팀의 기능들
3.2.1 개발 시간을 관리하는 방법
3.2.2 타사 소프트웨어의 위험 부담을 관리하는 방법
3.2.3 컨설턴트를 관리하는 방법
3.2.4 딱 적당하게 회의하는 방법
3.2.5 무리 없이 정직하게 반대 의견을 내는 방법
3.3 판단 능력
3.3.1 개발 시간에 맞춰 품질을 조절하는 방법
3.3.2 소프트웨어 시스템의 의존성을 관리하는 방법
3.3.3 소프트웨어의 완성도를 판단하는 방법
3.3.4 구입과 개발 사이에서 결정하는 방법
3.3.5 전문가로 성장하는 방법
3.3.6 면접 대상자를 평가하는 방법
3.3.7 화려한 전산 과학을 적용할 때를 아는 방법
3.3.8 비기술자들과 이야기하는 방법
4 상급자
4.1 기술적 판단 능력
4.1.1 어려운 것과 불가능한 것을 구분하는 방법
4.1.2 내장 언어를 활용하는 방법
4.1.3 언어의 선택
4.2 현명하게 타협하기
4.2.1 작업 일정의 압박과 싸우는 방법
4.2.2 사용자를 이해하는 방법
4.2.3 진급하는 방법
4.3 팀을 위해 일하기
4.3.1 재능을 개발하는 방법
4.3.2 일할 과제를 선택하는 방법
4.3.3 팀 동료들이 최대한 능력을 발휘하게 하는 방법
4.3.4 문제를 나누는 방법
4.3.5 따분한 과제를 다루는 방법
4.3.6 프로젝트를 위한 지원을 얻는 방법
4.3.7 시스템이 자라게 하는 방법
4.3.8 대화를 잘 하는 방법
4.3.9 사람들에게 듣고 싶어 하지 않는 말을 하는 방법
4.3.10 관리상의 신화들을 다루는 방법
4.3.11 조직의 일시적 혼돈 상태를 다루는 방법
5 참고 문헌
5.1 책
5.2 웹 사이트
6 역사 (2003년 5월 현재) / History (As Of May, 2003)
6.1 피드백 및 확장 요청 / Request for Feedback or Extension
6.2 원본 / Original Version
6.3 원저자의 경력 / Original Author's Bio

1 도입

좋은 프로그래머가 되는 것은 어렵고도 고상한 일이다. 소프트웨어 프로젝트의 공동 비전을 현실화하려고 할 때 가장 어려운 부분은 함께 일하는 개발자들과 고객들을 상대하는 일이다. 컴퓨터 프로그램을 짜는 것은 중요한 일이고 지식과 기능이 많이 드는 일이다. 하지만 그것은 좋은 프로그래머가 고객 및 자기가 크고 작게 책임을 지고 있는 수많은 동료들을 만족시키는 소프트웨어 시스템을 만들기 위해 해야 하는 다른 모든 일들에 비교해 볼 때 정말 어린아이 장난과 같다. 나는 내가 스물 한 살이었을 때 누군가가 나에게 설명해 주길 바랐던 것들을 가능한 한 간결하게 요약하려고 했다.

이것은 매우 주관적이며, 따라서 이 글은 개인적이고 다소 고집스럽게 보일 수밖에 없다. 이 글은 프로그래머가 일하면서 맞부딪치기 아주 쉬운 문제들에 한정되어 있다. 이런 문제들과 이에 대한 해결책은 사람 사는 데서 흔히 볼 수 있기 때문에 이 글이 설교처럼 보일 수도 있다. 그럼에도 불구하고 이 글이 유용하게 쓰이길 바란다.

컴퓨터 프로그래밍은 여러 강좌를 통해 배울 수 있다. The Pragmatic Programmer <Prag99>, Code Complete <CodeC93>, Rapid Development <RDev96>, Extreme Programming Explained <XP99> 등의 훌륭한 책을 통해 컴퓨터 프로그래밍에 대해 배우고, 좋은 프로그래머란 무엇인가에 대한 다양한 논점들을 알게 된다. 폴 그레이엄(Paul Graham) <PGSite>과 에릭 레이먼드(Eric Raymond) <Hacker>의 글은 이 글을 읽기 전이나 읽는 도중에 꼭 읽어 보아야 한다. 이 글은 이상의 훌륭한 글들과 달리 사회 활동의 문제를 강조하고 있으며 내가 보기에 꼭 필요하다고 생각하는 모든 기능들을 폭넓게 요약하고 있다.

이 글에서 "상사"는 나에게 프로젝트를 배정해 주는 사람을 의미한다. 사업, 회사, 부족(tribe)이라고 할 때, 사업이 돈을 버는 것, 회사가 현시대의 일터, 부족이 충성심을 공유하는 사람들이라는 뜻을 내포하는 것 외에는 모두 같은 뜻으로 사용했다.

우리 부족에 온 것을 환영한다.

2 초보자

2.1 개인적 기능들

2.1.1 디버그 배우기

디버깅은 프로그래머의 기본이다. 디버그란 말의 처음 뜻은 오류를 제거하는 것이지만, 더 중요한 것은 프로그램이 실행될 때 그것을 상세히 검사하는 일이다. 효과적으로 디버그 할 줄 모르는 프로그래머는 앞을 못 보는 것과 같다.

이상주의자라면 설계, 분석, 복잡도 이론 등등이 더 기본적인 것이라고 생각할 것이다. 하지만 이들은 현업의 프로그래머가 아니다. 현업의 프로그래머는 이상적인 세계에서 살고 있지 않다. 완벽한 프로그래머라 해도, 그는 대형 소프트웨어 회사, GNU 등의 조직, 자기 동료들이 만든 코드들에 둘러싸여 있고 그것을 가지고 작업해야 한다. 이 코드들은 대부분 불완전하며 불완전하게 문서화되어 있다. 어떤 코드가 실행될 때 그것을 꿰뚫어 볼 수 있는 능력이 없다면 사소한 문제에도 대책 없이 나가떨어질 수밖에 없다. 이러한 투시력은 실험해 보는 것을 통해 얻어지며, 그것이 바로 디버깅이다.

디버깅은 프로그램의 실행에 대한 것이지 프로그램 자체에 대한 것이 아니다. 대형 소프트웨어 회사에서 프로그램을 구입했다면 보통은 프로그램을 들여다볼 수 없다. 하지만 그 코드가 문서대로 동작하지 않거나 (컴퓨터가 멈춰 버리는 것은 아주 흔하고 극적인 예이다) 문서에 원하는 내용이 없는 일은 항상 일어난다. 더 흔한 것은, 오류가 생겨서 자기가 짠 코드를 검사하는데 어떻게 그런 오류가 생길 수 있는지 전혀 실마리를 잡을 수 없는 경우이다. 당연히 이것은 그 프로그래머가 짐작하고 있는 것 중 어떤 것이 잘못됐거나, 예상하지 못했던 상황이 발생하기 때문이다. 가끔은 소스 코드 응시하기 마법으로 문제를 해결할 수 있다. 하지만 그 마법이 통하지 않을 때는 디버그를 해야 한다.

프로그램의 실행에 대한 투시력을 얻기 위해서는 코드를 실행하면서 무엇인가를 관찰해야 한다. 어떤 경우에는 그것이 화면에 나타나서 눈으로 볼 수도 있지만, 다른 많은 경우에는 코드 내의 변수의 상태, 현재 실행되고 있는 코드의 줄 수, 복잡한 자료 구조에서 어떤 검증 조건(assertion)이 계속 유지되는가의 여부 등과 같이 눈에 보이지 않는다. 이런 숨겨진 것들은 드러나야 한다. 실행되고 있는 프로그램의 내부를 들여다보기 위해 널리 쓰이는 방법을 다음과 같이 분류할 수 있다.
  • 디버깅 도구 이용
  • 프린트 줄 넣기(printlining) -- 프로그램을 임시로 고치는 것, 특히 필요한 정보를 프린트하는 줄을 추가하는 것
  • 로그 기록 -- 로그 파일의 형태로 프로그램을 들여다볼 수 있는 영구적인 창을 만드는 것

디버깅 도구는 제대로 동작한다면 훌륭한 것이지만, 그 다음의 두 가지 방법이 더욱 더 중요하다. 디버깅 도구는 종종 언어의 발달을 따라가지 못하기 때문에 어떤 시점이 되면 쓸모 없게 될 수 있다. 그리고 디버깅 도구 자체가 프로그램의 수행 방식을 미묘하게 변화시킬 수도 있기 때문에 모든 경우에 유효한 것이 아니다. 끝으로, 대규모 자료 구조에 대한 검증 조건을 검사하는 것과 같은 디버깅의 경우에는 어떻든 간에 코드를 새로 짜야 한다. 디버깅 도구가 안정적이라면 그것의 사용 방법을 아는 것이 좋은 일이겠지만, 그 다음의 두 가지 방법을 쓸 줄 아는 것은 필수불가결한 일이다.

어떤 초보자는 코드를 고치고 실행하는 일을 반복해야 하는 디버깅에 대한 두려움이 잠재의식 속에 있는 것 같다. 이해할 만한 일이다. 이것은 처음 외과 수술을 하는 것과 비슷해 보인다. 하지만 초보자들은 코드가 쌩쌩 돌아가게 하기 위해 여기저기 찔러 보는 것에 익숙해져야 한다. 그들은 코드를 가지고 실험해 보는 것에 익숙해져야 하고, 자기가 코드를 가지고 무엇을 하더라도 그것이 문제를 악화시키지 않는다는 것을 배워야 한다. 이 소심한 사람들의 교사나 사수라면, 어떻게 해야 하는지 친절하게 알려 주고 손이라도 잡아 이끌면서 그 두려움을 극복할 수 있도록 도와 주라. 그 두려움 때문에 좋은 프로그래머가 될 수 있는 사람들도 아슬아슬하게 시작했다가 포기하는 경우가 많다.

2.1.2 문제 공간을 나눠서 디버그 하는 방법

디버깅은 수수께끼에서 출발하기 때문에 재미있다. 이렇게 디버그 하면 저렇게 될 것이라고 생각하지만 실제로는 다른 결과가 생긴다. 디버깅은 만만한 일이 아니다. 여기에서 무슨 사례를 제시하든 그것은 실제 상황에 비하면 상당히 부자연스운 것이 될 것이다. 디버깅은 창의력과 독창성이 필요하다. 디버깅에 한 가지 열쇠가 있다면 그것은 수수께끼에 대한 분할 정복 기법(divide and conquer technique)을 사용하는 것이다.

예를 들어 열 가지 일을 차례로 하는 프로그램을 만들었다고 하자. 그런데 실행해 보니 멈춰 버렸다. 멈추도록 프로그램하지 않았는데 말이다. 이제 "프로그램이 멈춘다"는 수수께끼가 생긴 것이다. 출력된 결과를 보면 처음 7번까지는 제대로 실행된 것을 알 수 있다. 나머지 세 가지가 출력 결과에서 안 보인다. 이제 우리의 수수께끼는 "프로그램이 8번이나 9번이나 10번에서 멈췄다"로 줄어들었다.

그럼 프로그램이 어디에서 멈췄는지 알아볼 수 있는 실험을 설계할 수 있을까? 물론이다. 디버거를 쓸 수도 있고 8번과 9번 다음에 프린트 줄을 넣을 수도 있다. (물론 사용하는 언어에 적합한 다른 방법을 쓸 수도 있다.) 그리고 다시 실행해 보면 우리의 수수께끼는 "프로그램이 9번에서 멈췄다"와 같이 더 줄어든다. 어느 순간에든 수수께끼가 정확히 무엇인지 기억하는 것은 집중하는 데 도움이 된다. 여러 사람이 급하게 어떤 문제에 매달려 있을 때는 그 일이 무척 혼란스러워질 수 있다.

분할 정복이라는 열쇠는 디버깅 기법일 뿐만 아니라 알고리듬 설계 기법이기도 하다. 수수께끼의 중간을 둘로 나누는 것만으로 일 처리를 잘 할 수 있다면, 더 이상 많이 나눌 필요는 없을 것이고 디버깅도 더 빨리 끝날 것이다. 그런데 수수께끼의 중간이란 어디쯤을 말하는 것인가? 여기가 바로 창의력과 경험이 필요한 지점이다.

아직 초보인 사람에게는, 모든 오류가 존재하는 공간이 소스 코드의 모든 몇 줄뿐인 것처럼 보일 것이다. 그는 아직 프로그램의 다른 차원, 즉, 줄들이 실행되는 공간, 자료 구조, 메모리 관리, 외부 코드와 상호작용, 문제가 생길 만한 코드와 간단한 코드 등을 볼 수 있는 감각이 없다. 이런 다른 차원들은 경험이 쌓인 프로그래머에게 문제를 일으킬 수 있는 모든 것들에 대해 완벽하지는 않지만 매우 유용한 머리 속의 모형(mental model)을 형성하게 해 준다. 이런 모형을 머리 속에 갖고 있으면 수수께끼의 중간이 어디인지 효과적으로 찾는 데 도움이 된다.

문제를 일으킬 수 있는 모든 것들의 공간을 둘로 균등하게 나눴다면, 이제는 그 둘 중 어느 쪽에서 오류가 생겼을지 결정해야 한다. 수수께끼가 "프로그램을 멈추게 하는 그 줄은 이 줄이 실행되기 전에 실행됐을까, 후에 실행됐을까?"와 같이 단순한 경우에는 어느 줄이 실행되는지 관찰하기만 하면 된다. 다른 경우에는 수수께끼가 이런 식으로 분할될 것이다. "저 그래프에 잘못된 노드를 가리키는 포인터가 있거나, 그 그래프에 변수들을 추가하는 알고리듬에 문제가 있다." 이런 경우에는 분할된 수수께끼 중 어느 쪽을 버릴 것인지 결정하기 위해 그 그래프의 포인터들이 모두 정확한지 알아보는 작은 프로그램을 작성해야 할 수도 있다.

2.1.3 오류를 제거하는 방법

나는 의도적으로 프로그램의 실행을 점검하는 행위와 오류를 고치는 행위를 구분하고 있다. 물론 디버깅은 버그를 제거하는 것을 뜻한다. 이상적으로는 코드를 완벽하게 이해하여 오류의 정체와 그것을 고칠 방법을 완벽하게 알게 되면서 "아하!" 하고 외치는 순간에 이를 수도 있다. 하지만 문서화가 잘 되어 있지 않아 그 속을 들여다 볼 수 없는 시스템들을 가지고 프로그램을 만드는 경우도 종종 있으므로 이런 일이 항상 가능한 것은 아니다. 또한 코드가 너무 복잡해서 그것을 완벽하게 이해할 수 없는 경우도 있다.

버그를 고칠 때에는 가능한 한 조금만 수정하여 버그를 고치고 싶을 것이다. 그러면서 성능 개선이 필요한 다른 것들을 보게 될 수도 있다. 하지만 이것들을 동시에 고치지는 말라. 한 번에 단 한 가지만 변경하는 과학 실험 방법을 사용하도록 하라. 이를 위한 최선의 과정은, 그 버그를 쉽게 다시 확인할 수 있게 되면, 고친 내용을 바꿔 넣고 나서, 프로그램에서 버그가 더 이상 없다는 것을 확인하는 것이다. 물론 때때로 한 줄 이상을 고쳐야 하겠지만 그렇다 해도 개념적으로는 더 이상 나눌 수 없는(atomic) 한 부분만 변경해서 버그를 고쳐야 한다.

실제로 버그가 여러 개인데 그것들이 하나인 것처럼 보이는 경우도 있다. 버그를 어떻게 정의하여 그것들을 하나씩 고쳐 갈 것인지 결정하는 것은 결국 프로그래머의 몫이다. 프로그램이 무엇을 해야 하는지, 또는 원래 개발자가 의도했던 것이 무엇인지 불분명할 경우도 있다. 그런 경우에는 경험을 근거로 판단을 내리고 그 코드에 자기 나름대로 의미를 부여해야 할 것이다. 그 프로그램이 무엇을 해야 할지 결정하고, 그것에 대해 주석을 달거나 어떤 식으로든 명료화하고, 그 코드가 그 의미에 부합하도록 만든다. 이것은 중급에서 고급의 기능으로서 처음부터 새로운 함수를 작성하는 것보다 더 어려울 경우도 있지만, 현업에서는 이런 귀찮은 일이 종종 생긴다. 어쩌면 자신에게 수정 권한이 없는 시스템을 고쳐야 하게 될지도 모른다.

2.1.4 로그를 이용해서 디버그 하는 방법

로그 기록(logging)이란 정보를 제공하는 일련의 기록인 로그(log)를 생성하도록 시스템을 작성하는 활동을 말한다. 프린트 줄 넣기(printlining)는 간단한, 보통은 일시적인, 로그를 생성하기만 한다. 완전한 초보자들은 프로그래밍에 대해 아는 것에 한계가 있기 때문에 로그를 이해하고 사용해야 한다. 시스템 설계자들은 시스템의 복잡성 때문에 로그를 이해하고 사용해야 한다. 로그가 제공하는 정보의 양은, 이상적으로는 프로그램이 실행되는 중에도, 설정 가능해야 한다. 일반적으로 로그 기록은 다음의 이점이 있다.
  • 로그는 재현하기 힘든 (예를 들어, 개발 완료된 환경에서는 발생하지만 테스트 환경에서는 재현할 수 없는) 버그에 대한 유용한 정보를 제공할 수 있다.
  • 로그는, 예를 들어, 구문(statement)들 사이에 걸리는 시간과 같이, 성능에 관한 통계와 정보를 제공할 수 있다.
  • 설정이 가능할 때, 로그는 예기치 못한 특정 문제들을 디버그하기 위해, 그 문제들을 처리하도록 코드를 수정하여 다시 적용하지(redeploy) 않아도, 일반적인 정보를 갈무리할 수 있게 한다.

로그에 남기는 정보의 양은 항상 정보성과 간결성 사이의 타협으로 결정된다. 정보를 너무 많이 남긴다면 로그가 낭비적이 되고 스크롤에 가려지게(scroll blindness) 되어, 필요한 정보를 찾기 어려워질 것이다. 너무 조금 남긴다면 필요한 정보가 남지 않을 것이다. 이런 점에서, 무엇이 출력될지 설정할 수 있게 하는 것은 매우 유용하다. 일반적으로 로그에 남는 기록을 통해, 그 기록을 남긴 소스 코드의 위치, (적용 가능하다면) 문제가 되는 작업을 실행한 쓰레드, 정확한 실행 시각, 그리고 일반적으로, 어떤 변수의 값, 여유 메모리의 양, 데이터 객체의 개수 등 그 밖의 유용한 정보를 알 수 있다. 이러한 로그 생성 구문들은 소스 코드 전체에 흩어져 있는데, 특히 주요 기능이 있는 지점과 위험 부담이 있는 코드 근처에 있다. 구문마다 수준이 정해질 수 있으면 시스템의 현재 설정에 따라 그 수준에 해당하는 기록만 남기게 될 것이다. 로그 생성 구문을 설계할 때에는 어디에서 문제가 생길지 예상해서 그것을 기록으로 남길 수 있게 해야 한다. 성능을 측정할 필요성도 예상하고 있어야 한다.

영구적인 로그를 남긴다면, 로그 기록이 프린트 줄 넣기(printlining)를 대신 할 수 있을 것이고, 디버그 구문들 중에도 로그 기록 시스템에 영구적으로 추가할 것들이 있을 것이다.

2.1.5 성능 문제를 이해하는 방법

실행중인 시스템의 성능을 알아내는 방법을 이해하는 일은 디버깅과 마찬가지로 피할 수 없는 일이다. 자기가 작성한 코드의 실행에 드는 비용을 완벽하고 정확하게 이해하고 있다 해도, 그 코드는 통제할 수 없거나 들여다볼 수 없는 다른 소프트웨어 시스템들을 호출할 때도 있다. 하지만 실제로 성능의 문제는 일반적으로 디버깅과는 조금 다르고 또 조금은 쉬운 문제이다.

어떤 시스템이나 하위 시스템이 너무 느린 것 같다고 가정해 보자. 그것을 빠르게 하기 전에 우선 왜 그것이 느린지에 대해 머리 속으로 모형을 만들어야 한다. 그렇게 할 수 있도록 시간과 그 밖의 자원들이 어디에 실제로 쓰이고 있는지 알기 위해 성능 기록 도구(profiling tool)나 좋은 로그 기록을 쓸 수 있다. 유명한 격언 중에 90%의 시간은 10%의 코드에 쓰인다는 말이 있다. 나는 그 격언에 성능 문제에서 입출력 시간(I/O)의 중요성을 추가하고 싶다. 종종 대부분의 시간은 이러저러한 방식으로 I/O에 쓰인다. 낭비가 심한 I/O와 그러한 10%의 코드를 찾아냈다면 한 발짝 잘 내딛은 것이다.

컴퓨터 시스템의 성능에는 여러 차원이 있고 여러 자원들이 사용된다. 측정해야 할 첫 번째 자원은 벽시계 시간(wall-clock time), 즉 계산에 걸리는 총 시간이다. 벽시계 시간을 로그에 기록으로 남기는 것은, 다른 성능 기록 방법이 통하지 않는 예상치 못한 상황에 대한 정보를 줄 수 있으므로 특히 가치가 있다. 하지만 이것으로 모든 것을 알 수는 없다. 때로는 시간이 좀 더 걸리기는 하지만 프로세서의 처리 시간을 많이 잡아먹지 않는 코드가 실제로 작업해야 하는 전산 환경에서는 훨씬 더 좋을 수 있다. 마찬가지로, 메모리, 네트웍 대역폭, 데이터베이스, 그 밖의 서버 접속들이 결국에는 프로세서 처리 시간보다 더 낭비가 클 수 있다.

동시에 사용해야 하는 공유 자원 쟁탈(contention)로 교착 상태(deadlock)나 기아 상태(starvation)가 생길 수도 있다. 교착 상태란 동기화나 자원 요청이 부적절하여 더 이상 진행할 수 없는 상태를 말한다. 기아 상태란 구성요소에 시간을 적절히 배분하는 데에 실패한 것이다. 이런 상황을 예상할 수 있다면 프로젝트를 시작할 때부터 이런 쟁탈을 측정할 방법을 마련하는 것이 최선이다. 이러한 쟁탈이 일어나지 않는다 해도 그것을 확실히 검증할 수 있게 해 놓는 것은 매우 도움이 된다.

2.1.6 성능 문제를 해결하는 방법

대부분의 소프트웨어 프로젝트는 첫 배포판을 냈을 때보다 비교적 적은 노력으로도 10배에서 100배나 더 빠르게 진행될 수 있다. 출시일의 압박 하에서는, 일을 간단하고 신속하게 끝낼 수 있는, 하지만 다른 해결책보다는 효율이 떨어지는, 해결책을 선택하는 것이 어쩌면 현명하고도 효과적인 방법일 수 있다. 하지만 성능은 사용 편이성(usability)의 일부이며, 결국에 가서는 더욱 세심하게 고려해야 할 경우가 많다.

매우 복잡한 시스템의 성능을 향상시키는 열쇠는 병목(bottleneck), 즉 대부분의 자원들이 사용되는 지점을 찾기 위해 충분히 잘 분석하는 것이다. 계산 시간의 1% 밖에 차지하지 않는 함수를 최적화하는 것은 별 의미가 없다. 실제로 시간이 어디에 쓰이는지 알아내기 위해 성능 분석을 먼저 해야 하며, 그로부터 무엇을 향상시킬 것인지 결정할 수 있다. 경험상으로 볼 때, 어떤 작업이 시스템이나 시스템의 중요한 부분을 최소한 두 배 빠르게 할 것이라고 생각되지 않는다면, 그것을 실행에 옮기기 전에 신중하게 생각해야 한다. 보통 이것을 위해 쓰는 방법이 있다. 그 변화에 따라 필요하게 되는 검사와 품질 확인의 수고를 고려하라. 모든 변화에는 검사라는 짐이 따르므로 큰 변화가 적을수록 더 좋은 것이다.

어디에선가 두 배의 향상을 달성한 후에는, 최소한 다시 생각하고 또 다시 분석하여 그 다음으로 낭비가 심한 병목이 어디인지 발견해 내고, 또 다른 두 배의 성능 향상을 이루기 위해 그 지점을 공략해야 할 것이다.

성능 상 병목은 소를 셀 때 머리를 세는 대신 다리를 센 다음 4로 나누는 것에 비유할 수 있을 것이다. 예를 들어, 나는 어떤 관계형 데이터베이스 시스템에서 자주 검색하는 열에 적절한 인덱스를 달지 않아서, 검색이 최소한 스무 배는 느려지는 오류를 일으킨 적이 있다. 그 외에도, 내부 반복문에서 불필요한 I/O를 하는 것, 더 이상 필요 없는 디버그 구문을 남겨 놓는 것, 불필요한 메모리 할당, 성능에 대해 제대로 문서화되어 있지 않은 라이브러리나 그 밖의 하위 시스템들을 전문적인 안목 없이 사용하는 것 등을 예로 들 수 있을 것이다. 이런 식의 성능 향상을, 쉽게 따서 성과를 낼 수 있다는 의미에서, 낮게 달린 과일(low-hanging fruit)이라고 부르기도 한다.

낮게 달린 과일들을 거의 다 따 버렸다면 어떻게 할 것인가? 아마도 더 높이 손을 뻗거나 나무를 베어 내릴 것이다. 즉, 조그만 성능 향상을 계속해 갈 수도 있고, 시스템이나 하위 시스템을 진지하게 재설계할 수도 있을 것이다. (이것은, 새로운 설계라는 측면뿐만 아니라 자기 상사에게 이것이 좋은 생각이라는 것을 설득한다는 측면에서도 좋은 프로그래머로서 자신의 능력을 발휘할 수 있는 훌륭한 기회이다.) 하지만, 재설계를 주장하기 전에는 이것이 하위 시스템을 다섯 배에서 열 배는 더 낫게 할 수 있는지 스스로 질문해 봐야 한다.

2.1.7 반복문을 최적화하는 방법

때때로 제품에서 실행하는 데 시간이 오래 걸리거나 병목(bottleneck)이 되는 반복문이나 재귀함수를 보게 될 것이다. 그 반복문을 조금 빠르게 고치려고 하기 전에, 혹시 그것을 완전히 제거할 방법은 없는지 잠시 생각해 보라. 다른 알고리듬으로 그 일을 할 수 없을까? 다른 계산을 하면서 동시에 그 계산을 할 수는 없을까? 그런 식의 방법을 찾을 수 없다면 반복문 최적화 작업을 해도 된다. 이 일은 단순하다. 잡동사니를 없애 버려라. 결국 이 일은 독창성뿐만 아니라 그런 종류의 구문이나 식에 드는 비용에 대한 이해가 요구된다. 여기 몇 가지를 제안해 보겠다.
  • 실수 연산(floating point operation)을 제거하라
  • 필요 없이 메모리 블록을 새로 할당하지 말라
  • 상수들을 미리 계산하라(fold together)
  • I/O는 버퍼로 옮겨라
  • 나눗셈을 피하라
  • 쓸데없는 형변환(cast)을 피하라
  • 첨자(index)를 반복 계산하는 것보다는 포인터를 옮기는 것이 낫다

이러한 연산에 드는 비용은 사용하는 시스템에 따라 다르다. 어떤 시스템의 컴파일러와 하드웨어는 알아서 이런 일들을 해 준다. 그래도 분명하고 효율적인 코드가 특정 시스템에 대한 이해가 필요한 코드보다 더 낫다.

2.1.8 I/O 비용을 다루는 방법

많은 문제에서 프로세서는 하드웨어 장치들과 통신하는 데 드는 시간에 비해 빠르게 동작한다. 이런 시간 비용을 보통 줄여서 I/O라고 하고, 여기에는 네트웍 시간, 디스크 I/O, 데이터베이스 질의, 파일 I/O, 그리고 프로세서에 가깝지 않은 어떤 하드웨어가 뭔가를 하게 하는 작업들이 포함된다. 따라서 빠른 시스템을 만든다는 것은, 어떤 빽빽한 반복문 속에 있는 코드를 개선하거나 더 나아가 알고리듬을 개선하는 것보다 I/O를 개선하는 일이 될 경우가 많다.

I/O를 개선하는 두 가지 매우 기초적인 방법, 즉 캐쉬와 효율적 데이터 표현(representation)이 있다. 캐쉬는 (일반적으로 어떤 추상적인 값을 읽어 오는) I/O를 피하기 위해 그 값을 가깝게 복사해 놓아서 그 값을 가져오기 위해 I/O를 다시 발생시키지 않는 것을 말한다. 캐쉬의 핵심은 어떤 데이터가 원본(master)이고 어떤 데이터가 복사본인지 분명하게 구분하는 것이다. 원본은 단 하나만 있다! 캐쉬는 복사본이 원본의 변화를 즉시 반영하지 못하는 경우가 있다는 위험 부담이 있다.

효율적 데이터 표현이란 데이터를 더욱 효율적으로 표현하여 I/O의 비용을 줄이는 방식이다. 이 방법은 종종 가독성과 호환성 등의 다른 요구 조건과 대립되기도 한다.

효율적 데이터 표현은 처음의 표현 방식보다 두세 배의 개선 효과가 있기도 하다. 이를 위한 방법들로는 사람이 읽을 수 있는 표현 방식 대신 2진 표현 방식을 사용하는 것, 데이터와 함께 심벌 사전을 전송하여 긴 심벌들을 인코딩할 필요가 없게 하는 것, 그리고 극단적으로는 허프만(Huffman) 인코딩 같은 것 등이 포함된다.

때때로 가능한 세 번째 방법은 계산 부분을 데이터에 밀착시켜 더 가까운 곳에서 참조할 수 있게 하는 것이다. 예를 들어, 데이터베이스에서 어떤 데이터를 읽어 와서 합계와 같은 간단한 계산을 한다고 할 때 데이터베이스 서버가 직접 그 작업을 하게 하는 것이다. 이 방법은 작업하는 시스템의 특성에 매우 많이 의존하기는 하지만, 시험해 볼 필요는 있다.

2.1.9 메모리를 관리하는 방법

메모리는 절대로 다 써 버리면 안 되는 소중한 자원이다. 잠시 동안은 그것을 무시할 수 있겠지만 결국에는 메모리를 어떻게 관리할 것인지 결정해야 할 것이다.

단일 서브루틴이 차지하는 범위 이상으로 유지될 필요가 있는 공간을 종종 할당된 힙(heap)이라고 한다. 아무도 참조하지 않는 메모리 영역은 쓸모없는 쓰레기(가비지, garbage)일 뿐이다. 사용하는 시스템에 따라 메모리가 가비지가 될 것 같으면 명시적으로 메모리 할당을 해제해야 한다. 가비지 수집기를 제공하는 시스템을 사용할 수 있을 경우도 많다. 가비지 수집기는 가비지를 발견하면 프로그래머의 어떤 조작도 필요 없이 그것이 차지하는 공간을 풀어준다. 가비지 수집은 훌륭한 방법이다. 이를 통해 오류를 줄이고, 적은 노력으로도 코드를 더욱 간결하게 할 수 있다. 할 수 있다면 이 방법을 사용하라.

하지만 가비지 수집을 한다 해도 모든 메모리를 가비지로 채우게 될 수 있다. 고전적인 실수 중의 하나는 해쉬 테이블(hash table)을 캐쉬로 사용하고는 해쉬 테이블에 있는 참조 주소들을 제거하는 것을 잊어버리는 것이다. 참조 주소가 남아 있으므로 그 주소에 해당하는 메모리 영역은 가비지 수집이 될 수 없는 상태로 못 쓰게 된다. 이것을 메모리 누수(leak)라고 한다. 메모리 누수는 일찍부터 찾아서 고쳐야 한다. 장시간 실행되는 시스템이 있다면 검사할 때는 메모리가 고갈되는 일이 없다가 실제로 사용될 때가 돼서야 고갈되기도 한다.

새로운 객체의 생성은 어떠한 시스템에서도 어느 정도 시간 비용이 드는 작업이다. 하지만 서브루틴의 지역 변수들(local variables)에 직접 할당된 메모리는 할당 해제 방식이 매우 간단해질 수 있으므로 그렇게 시간 비용이 들지는 않는다. 어떻든 불필요한 객체 생성은 피해야 한다.

한 번에 필요한 객체 수의 상한을 정할 때 생기는 중요한 경우가 있다. 이 객체들이 모두 같은 양의 메모리를 필요로 한다면 그것들을 모두 수용하기 위해 단일 블록의 메모리, 즉 버퍼를 할당할 수 있을 것이다. 그 객체들은 이 버퍼 안에서 정해진 순환 방식에 따라 할당되고 해제될 수 있으므로 이 버퍼를 링 버퍼(ring buffer)라고 부르기도 한다. 이것은 보통 힙 할당보다 더 빠르다.

때로는 할당된 공간이 다시 할당될 수 있도록, 가비지 수집에 의존하는 대신, 그것을 명시적으로 풀어줘야 한다. 그래서 할당된 각 영역을 잘 알아내어 그것을 적절한 때에 해제하는 방법을 설계해야 한다. 그 방법은 생성된 객체의 종류에 따라 달라질 수 있다. 메모리 할당 작업이 수행될 때마다 그것이 결국에는 메모리 할당 해제 작업과 짝을 이뤄야 한다는 사실을 명심해야 한다. 이것은 매우 어렵기 때문에 프로그래머들은 이를 위해 단순하게 참조 회수 세기와 같은 기초적인 형태의 가비지 수집 방법을 구현하는 경우도 있다.

2.1.10 가끔씩 생기는 버그를 다루는 방법

가끔씩 생기는 버그는 "외계에서 온 20미터짜리 투명 전갈"의 사촌 같은 종류의 버그이다. 이 끔찍한 악몽은 관찰하기 어려울 만큼 가끔씩 나타나지만, 또한 무시할 수 없을 만큼 자주 일어난다. 이런 버그는 발견하기도 어렵게 때문에 고치기도 어렵다.

그 버그를 찾기 위해 여덟 시간을 매달린 뒤에 그것이 정말 있는 것인지 의심하기 시작한다 해도, 가끔씩 생기는 버그는 다른 모든 것들이 따르는 동일한 논리 법칙을 따를 수밖에 없다. 이 버그를 상대하기 힘든 것은 알지 못하는 어떤 조건들에서만 생기기 때문이다. 그 버그가 생기는 바로 그 때의 상황들을 기록하여 정말로 어떤 변이가 생긴 것인지 추측할 수 있도록 해 보라. 그 조건은, 예를 들어, '이 버그는 '와이오밍(Wyoming)'이라는 값을 입력했을 때만 생긴다"는 것과 같이, 데이터 값에 관련되어 있을 수도 있다. 만약 이것이 변이의 원인이 아니라면 다음으로는 동시에 수행되어야 하는 작업이 실제로는 그렇게 되지 못했을 경우를 의심해 볼 수 있다.

어떤 통제된 방식으로 그 버그가 다시 나타나도록 계속, 계속, 계속 시험해 보라. 다시 나타나게 할 수 없다면, 버그가 실제로 발생할 때 그것을 분석하기 위해 필요하다고 생각되는 정보들을 기록으로 남길 수 있는 (필요하다면 특별한) 로그 기록 시스템을 만들어 그 버그 앞에 덫을 놓아 보라. 버그가 개발 환경에서는 나타나지 않고 완성 제품에서만 나타난다면 그것은 오래 걸리는 프로세스 때문일 것이라고 믿어 보라. 로그 기록에서 얻는 실마리들은 해결책을 제공해 주지는 못하더라도 로그 기록 방법을 개선하기 위한 정보는 충분히 줄 수 있을 것이다. 개선된 로그 기록 시스템을 완성하는 데에는 오랜 시간이 걸릴 수도 있다. 그리고는 더 많은 정보를 얻기 위해 그 버그가 다시 나타날 때까지 기다려야 한다. 이 일은 어느 시간 동안 계속 반복해야 할 수도 있다.

가끔씩 생기는 버그들 중에서 내가 저지른 가장 어리석었던 것은, 어떤 수업의 프로젝트에서 함수형 프로그래밍 언어(functional programming language)를 다중 쓰레드로 구현하는 것이었다. 나는 그 함수형 프로그램이 모든 (이 수업에서는 여덟 개의) CPU들을 잘 활용하여 수치 계산을 동시에 정확하게 해 내도록 매우 주의를 기울였다. 그런데 가비지 수집기를 동기화하는 것을 깜빡 잊었다. 이 시스템은 오랜 시간 동안 잘 돌아갔고, 뭔가 이상하다는 것을 눈치 채기 전까지는, 어떤 작업을 시작하든 잘 마무리되는 것 같았다. 부끄럽게도 나는 내 실수가 드러나기 전까지는 하드웨어에 문제가 있다고 생각했었다.

최근에 일하면서는, 발견하기까지 몇 주나 걸렸던 가끔씩 생기는 버그를 만난 적이 있다. 우리에게는 아파치(Apache) 웹 서버 뒤에 자바(Java)로 구현된 다중 쓰레드 어플리케이션 서버들이 있다. 페이지 전환을 빠르게 유지하기 위해 우리는 모든 I/O가 페이지 전환 쓰레드들과는 다른 네 개의 독립된 쓰레드에서 일어나게 하고 있다. 그런데 이것들이 (우리 로그 기록에 따르면) 가끔 한 번씩 몇 시간 동안 멈춘 것처럼 되면서 아무 일도 하지 않았다. 쓰레드가 네 개가 있기 때문에 네 개 모두 멈추지 않는 한 그 자체로는 큰 문제는 아니었다. 그렇게 된다면 이 쓰레드들이 비워내는 큐(queue)가 남아 있는 모든 메모리를 순식간에 다 채워서 서버가 멈춰버렸을 것이다. 이것을 알게 되기까지 한 주 정도 걸렸지만 무엇 때문에 이런 일이 생기는지, 언제 생길지, 또는 멈출 때 어느 쓰레드가 어디쯤 작업을 하고 있는지조차 여전히 몰랐다.

이것은 타사 소프트웨어와 연관된 위험성을 보여준다. 우리는 텍스트에서 HTML 태그를 제거하는 코드의 사용권을 받아서 쓰고 있었다. 우리는 그 코드가 나온 나라 이름을 따서 그것을 '프랑스 스트리퍼'라는 애칭으로 불렀다. 우리는 소스 코드를 가지고 있었지만 (감사하나이다!) 우리 서버의 로그 기록을 살펴보다가 이메일 쓰레드들이 프랑스 스트리퍼에서 멈춘다는 것을 알게 되기까지 그 소스를 주의깊게 연구하지 않았다.

이 스트리퍼는 제대로 동작했지만 길이가 길고 특이한 텍스트에 대해서는 그렇지 못했다. 이런 텍스트를 처리하는 데에는 텍스트 길이의 제곱에 비례하거나 더 많은 시간이 걸렸다. 이런 텍스트들이 자주 나타나는 것이었다면 우리는 그 버그를 금방 발견했을 것이다. 그것이 전혀 나타나지 않았다면 우리는 전혀 문제가 없었을 것이다. 하지만 그것은 나타났고, 우리는 문제를 이해하고 해결하는 데 몇 주나 걸렸던 것이다.

2.1.11 설계 기능을 익히는 방법

소프트웨어를 설계하는 방법을 배우기 위해서는 사수(mentor)가 설계를 할 때 그들과 한 자리에 있으면서 그들의 행동을 연구하라. 그리고 잘 작성된 소프트웨어를 연구하라. 그 후에는 최근에 나온 설계 기법에 대한 책을 읽으라.

그리고 그것들을 직접 실천해야 한다. 소규모 프로젝트부터 시작하라. 최종 작업을 마쳤으면 그 설계가 어떻게 실패하거나 성공했는지, 처음 생각했던 개념에서 어떻게 달라졌는지 숙고하라. 이런 식으로 다른 사람들과 더 큰 프로젝트를 수행하게 된다. 설계 기능은 판단 능력의 문제이며, 그것을 얻기까지 몇 년은 걸린다. 현명한 프로그래머는 두 달이면 적당한 수준에서 기본 기능을 익힐 수 있을 것이며 그것을 기반으로 발전해 갈 것이다.

자기 자신의 스타일을 개발하는 것은 자연스러운 일이며 자신에게 도움이 될 것이다. 설계는 예술이지 과학이 아니라는 것을 명심하라. 이런 주제로 책을 쓰는 사람들은 흔히 그것이 과학적으로 보이게 하는 데에 관심을 갖는다. 하지만 특정한 설계 스타일을 고집하지는 않도록 하라.

2.1.12 실험을 수행하는 방법

이제는 고인이 된 위대한 에처 다익스트라(Edsger Dijkstra)는 전산 과학은 실험 과학이 아니며<ExpCS> 전자적인 컴퓨터에 의존하지 않는다고 설득력 있게 설명했다. 그가 1960년대의 전산 과학에 대해 다음과 같이 말했다. <Knife>

해로운 일이 일어났다. 이 주제는 '전산 과학'으로 알려지기 시작했다. - 이것은 꼭 외과 수술을 '칼 과학'이라고 부르는 것과 같다. - 그리고 전산 과학은 기계와 그 주변 장치들에 대한 학문이라고 사람들의 생각 속에 단단히 자리를 잡게 되었다.


프로그래밍이 실험 과학이 되어서는 안 되겠지만, 대부분의 실무 프로그래머들은 다익스트라가 전산 과학에 대해 내린 정의에 동감할 만큼 여유롭지는 못하다. 우리는, 전부는 아니더라도 일부 물리학자들이 그러하듯이, 실험의 영역 속에서 일해야 한다. 지금부터 30년 동안 프로그래밍이 실험 없이 이뤄질 수 있다면, 그것은 전산 과학의 위대한 승리라고 할 수 있을 것이다.

앞으로 해 보게 될 몇 가지 실험들은 다음과 같다.
  • 문서 내용에 부합하는지 검증하거나 (문서가 없다면) 시스템의 반응을 이해하기 위해 몇몇 표본들로 시스템을 검사하기
  • 실제로 버그가 고쳐졌는지 알아보기 위해 코드의 바뀐 부분을 검사하기
  • 시스템의 성능 특성을 완벽하게 알기 위해 두 가지 다른 조건에서 시스템의 성능을 측정하기
  • 데이터의 무결성을 점검하기
  • 어려운, 혹은 반복하기 힘든 버그 해결의 실마리를 얻기 위해 통계 자료를 수집하기

내가 이 글에서 실험 설계를 설명할 수 있으리라고는 생각하지 않는다. 독자 스스로 연구하고 연습해 보아야 할 것이지만, 두 가지 짧은 조언을 해 줄 수는 있겠다.

첫째, 자신의 가설이나 점검하려고 하는 검증 조건(assertion)이 무엇인지 명쾌해야 한다. 혼돈스럽거나 다른 사람들과 함께 작업할 때는 가설을 적어 보는 것이 도움이 되기도 한다.

각 실험이 지난 실험에서 얻은 지식에 기반을 두는 일련의 실험들을 설계해야 하는 상황에 처할 때가 종종 있을 것이다. 그러므로 가능한 한 많은 정보를 얻어낼 수 있도록 실험을 설계해야 한다. 불행히도 이것은 각 실험이 단순명료해야 한다는 조건과 대립된다. 이런 상황에 대한 판단은 경험을 통해 개발해야 할 것이다.

2.2 팀의 기능들

2.2.1 시간 추정이 중요한 이유

소프트웨어 시스템이 최대한 빨리 적극적으로 활용되게 하기 위해서는 개발 계획을 세우는 것뿐만 아니라 문서화, 시스템 배치, 마케팅 등의 계획도 세워야 한다. 상업적인 프로젝트에서는 영업과 재무도 포함한다. 개발 시간을 예측할 수 없다면 이러한 것들을 효과적으로 계획하는 것은 불가능하다.

정확한 시간 추정은 예측 가능성을 높인다. 관리책임자들은 그렇게 되는 것을 매우 좋아하며, 또 당연히 그래야 한다. 관리책임자들은 소프트웨어를 개발하는 데 얼마나 시간이 걸릴지 정확하게 예측하는 것이 이론적으로나 실제적으로 불가능하다는 사실을 전혀 이해하지 못하는 경우가 있다. 우리는 이런 불가능한 일을 하도록 항상 요구받으므로 그 사실을 정직하게 대면해야 한다. 어쨌든 이 과제가 불가능하다는 것을 인정하지 않는 것은 부정직한 일이 될 것이므로 필요하다면 그것을 설명하라. 사람들은,

그 문제를 제대로 이해한 것이라면, (아무도 그 동안 우리를 방해하지 않는다고 할 때) 5주 안에 마칠 가능성이 50% 정도 있다고 추정됩니다.


위의 문장이 실제로는,

그 일을 모두 5주 안에 틀림없이 마치겠습니다.


위의 문장을 뜻한다고 부풀려 생각하는 놀라운 경향이 있으므로, 시간 추정에 대해 의사소통의 문제가 생길 가능성이 많다.

이러한 일상적인 해석의 문제가 있으므로 상사나 고객이 아무 것도 모른다 생각하고 그 시간 추정이 무엇을 의미하는 것인지 분명하게 논의해야 한다. 그리고 예상 시간이 아무리 틀림없어 보여도 다시 한 번 더 그것을 추정해 보아야 한다.

2.2.2 프로그래밍 시간을 추정하는 방법

시간 추정은 연습이 필요하다. 또한 노력해야 한다. 시간 추정은 노력이 많이 드는 일이기 때문에, 특히 대규모 작업 시간을 추정해야 할 경우에는 시간 추정 자체에 드는 시간을 추정할 필요도 있다.

대규모 작업 시간을 추정해야 할 때에는 천천히 하는 것이 가장 정직한 것이다. 대부분의 기술자들은 열심이 있으며 사람들을 기쁘게 하고 싶어 하기 때문에 천천히 하는 것을 분명히 좋아하지 않을 것이다. 하지만 즉석으로 추정한 것은 대개 정확하지도 정직하지도 않다.

천천히 하는 동안 그 과제를 수행하거나 과제의 모형을 만드는 것에 대해 숙고할 수 있을 것이다. 정책의 압력을 피할 수 있다면 이것이 시간 추정을 도출할 수 있는 가장 정확한 방법이며, 실제로도 그대로 작업이 진행될 수 있을 것이다.

조사할 시간을 충분히 갖는 것이 불가능할 때에는, 우선 그 추정이 무엇을 뜻하는지 아주 분명하게 확정해야 한다. 추정 내용 기록의 첫 부분과 마지막 부분에 그 의미를 다시 써 놓으라. 과제를 점진적인 소규모 하위 과제들로 분해하되 각 소규모 과제가 하루 이상이 되지 않을 때까지, 이상적으로는 그 이하의 길이로 추정 내용 기록을 준비하라. 가장 중요한 것은 하나라도 빠뜨리는 일이 없도록 하는 것이다. 예를 들어, 문서화, 검사, 계획 시간, 다른 그룹과 회의 시간, 휴가 시간 등이 모두 매우 중요하다. 매일 바보들을 상대하며 보내는 시간이 있다면 그것도 추정 내용에 한 항목으로 적어 넣으라. 이렇게 하면 최소한 무엇 때문에 시간을 써 버리게 되는지 상사가 알아볼 수 있게는 될 것이며, 자신의 시간을 지킬 수 있게 될 것이다.

추정 시간을 은연중에 불려서 쓰는 기술자들도 있지만, 나는 그렇게 하지 말라고 권하고 싶다. 불려서 쓰는 것이 낳는 결과들 중 하나는 다른 사람의 신뢰를 잃게 된다는 것이다. 예를 들어, 어떤 기술자가 사실은 하루가 걸릴 것이라고 생각되는 어떤 과제를 3일로 추정했다고 하자. 그 기술자가 나머지 이틀은 문서 작업을 하거나 어떤 다른 유용한 프로젝트를 하겠다고 계획했을 수 있다. 하지만 그 과제가 단 하루 만에 끝났다는 것은 (그 사실이 알려졌다면) 추적할 수 있을 것이며, 일을 늦추거나 과대 추정했다는 기색이 보일 것이다. 자신이 실제로 하고 있는 것을 적절히 볼 수 있게 하는 것이 더욱 좋다. 문서 작업하는 데 드는 시간이 코딩 시간의 두 배가 된다는 사실을 시간 추정 내용에서 확인할 수 있다면, 이것을 관리책임자가 볼 수 있게 하는 것이 더욱 큰 이득이 될 것이다.

추정 시간은 숨김없이 불려서 쓰라. 어떤 과제가 하루가 걸릴 것 같은데, 현재 접근 방법이 들어맞지 않을 경우 열흘이 걸릴 수도 있다면, 이 사실을 어떻게든 추정 내용에 적으라. 그렇지 않으면 적어도 추정한 확률을 가중치로 한 평균값을 계산하라. 알 수 있고 추정할 수 있는 위험 부담 요소는 모두 일정에 들어가야 한다. 한 사람이라면 주어진 기간 동안 아프지 않을 수 있다. 하지만 여러 기술자들이 함께 하는 큰 프로젝트에서는 환자가 생기는 시간도 있고 휴가 기간도 있다. 필수로 참석해야 하는 회사의 연수 세미나가 일어날 확률은 얼마나 될까? 추정할 수 있다면 꼭 끼워 넣으라. 물론 알려지지 않은 미지수의 일, 즉 예측불허의 일들도 있다. 예측불허란 개별적으로는 추정할 수 없다는 말이다. 모든 예측불허의 일에 대해 공통적으로 쓰이는 한 항목을 만들거나, 상사와 의사소통하는 그 밖의 방식으로 그것들을 처리할 수 있을 것이다. 하지만 상사가 그것들의 존재를 잊어버리게 하지는 말라. 예측불허의 일을 생각하지 않고 시간을 추정한 그대로 일정이 잡히기는 지독하게도 쉽다.

팀 환경에서는, 그 일을 하는 사람이 그 시간을 추정하도록 해야 하며, 전체 추정 시간에 대해 팀 전체가 합의하도록 해야 한다. 사람들은 능력, 경험, 준비도, 자신감에 큰 차이가 있다. 능력 있는 프로그래머가 자기에게 맞게 추정한 것을 능력이 부족한 프로그래머들도 따라 하다가는 큰 문제가 생긴다. 팀 전체가 추정 내용에 대해 한 줄, 한 줄씩 동의하게 함으로써 팀 전체의 이해를 분명히 하고, 자원을 전술적으로 재분배할 수 있는 (예를 들어, 능력이 부족한 팀원에게서 능력 있는 팀원에게 짐을 넘기는) 기회도 생긴다.

수치로 나타낼 수 없는 큰 위험 부담이 있다면, 프로그래머는 관리책임자가 함부로 거기에 뛰어들었다가 그 위험이 발생했을 때 당황하지 않도록 강력하게 말해 줄 의무가 있다. 바라기에는 그 경우에 그 위험 부담을 줄이기 위해 필요한 모든 일을 하게 될 것이다.

익스트림 프로그래밍(Extreme Programming) 기법을 사용하도록 회사를 설득할 수 있다면 비교적 적은 것들만 추정해도 될 것이며, 더 재미있으면서 더 생산적으로 일할 수 있을 것이다.

2.2.3 정보를 찾는 방법

알아야 하는 정보의 성격에 따라 그것을 찾는 방법이 결정된다.

어떤 소프트웨어의 최근 패치 단계와 같이 객관적이고 검증하기 쉬운 구체적인 것들에 대한 정보가 필요하다면, 그 정보에 대해 인터넷을 검색하거나 토론 그룹에 글을 올려 많은 사람들에게 공손히 물어 보라. 의견이나 주관적인 해석의 낌새가 있는 것은 절대 인터넷에서 검색하지 말라. 허튼 소리가 진실로 취급되는 비율이 매우 놓다.

어떤 것에 대한 생각이 변화해 온 역사와 같이 주관적인 것에 대한 일반적인 지식을 원한다면, (실제 건물이 있는) 도서관으로 가라. 예를 들어, 수학이나 버섯이나 신비주의에 대해 알고 싶다면 도서관으로 가면 된다.

사소하지 않은 어떤 일을 하는 방법을 알고 싶다면 그 주제에 대한 책을 두세 권 사서 읽으라. 소프트웨어 패키지를 설치하는 방법 같은 사소한 일을 하는 방법은 인터넷에서 배울 수 있다. 좋은 프로그래밍 기법 같은 중요한 것들을 인터넷에서 배울 수도 있지만, 그 결과를 검색하고 분류하거나 그 결과가 믿을 만한 것인지 파악하느라, 손으로 잡을 수 있는 책의 적당한 부분을 찾아 읽는 데 드는 시간보다 더 많은 시간을 낭비할 가능성이 크다.

"이 신제품 소프트웨어는 대규모의 데이터를 처리할 수 있는가?"와 같이 아무도 알 것 같지 않은 정보가 필요하다면, 어쨌든 인터넷이나 도서관을 검색해 봐야 할 것이다. 열심히 찾아도 찾을 수 없다면 그것을 확인하기 위한 실험을 설계해야 할 것이다.

어떤 특정한 상황에 대한 의견이나 가치 판단을 원한다면, 전문가에게 이야기하라. 예를 들어, 최신 데이터베이스 관리 시스템(DBMS)을 LISP로 만드는 것이 좋은 생각인지 알고 싶다면 LISP 전문가와 데이터베이스 전문가와 이야기해야 한다.

어떤 응용프로그램을 위한, 아직 발표되지 않은 더 빠른 알고리듬이 있는지 알고 싶다면 그 분야에서 일하는 사람과 이야기해야 할 것이다.

사업을 시작할 것인지 말 것인지에 대한 결정과 같이 자신만이 내릴 수 있는 개인적인 결정을 하려고 한다면, 그 생각에 대한 긍정적인 조건과 부정적인 조건을 나열해서 적어 보라. 그래도 결정을 못하겠다면 점(divination)을 쳐 보라. 어떤 생각에 대해 다각도로 연구해 봤고, 해야 할 일을 다 했고, 모든 결과와 긍정적, 부정적 조건들을 다 따져 봤다 해도, 아직은 결정하지 마라. 이제 머리는 잠시 쉬고 마음의 움직임을 따라 보라. 잘 의식하지 못하는 자신의 욕망을 파악하는 데에는 다양한 점 기법들이 유용하게 쓰일 수 있다. 이 기법들이 보여 주는 모호하고 무작위적인 패턴에 대해 자신의 잠재의식이 의미를 부여하게 될 것이기 때문이다.

2.2.4 사람들을 정보의 원천으로 활용하는 방법

각 사람의 시간을 존중하고 자신의 시간과 균형을 맞추라. 어떤 사람에게 질문하는 것은 답을 얻는다는 것 이상의 것을 이루게 한다. 그 사람은 나의 존재를 인정하고 특정 질문에 귀를 기울이면서 나에 대해 알게 된다. 나도 마찬가지로 그 사람을 알게 되며, 찾던 답도 알 수 있게 된다. 대개의 경우 이것이 질문 자체보다 더욱 중요하다.

하지만 그것을 반복하게 되면 그 가치는 줄어든다. 질문을 한다는 것은 결국 어떤 사람이 갖고 있는 소중한 필수품, 즉 그들의 시간을 사용하는 것이다. 의사소통의 소득은 그 비용과 비교하여 저울질해 보아야 한다. 게다가 그 비용과 소득은 사람마다 다르다. 나는 100명의 부하 직원이 있는 사람은 그 조직의 모든 사람들에게 한 달에 5분씩은 이야기할 시간이 있어야 한다고 확신한다. 이것은 자기 시간의 약 5%를 할애하는 것이 될 것이다. 하지만 10분은 너무 많은 것 같고, 같은 5분이라 해도 직원이 1,000명이라면 그것도 너무 많다. 자신의 조직에 속한 사람들에게 이야기하는 시간의 양은 그 사람들의 (직위보다는) 역할에 따라 다르다. 상사에게 이야기하는 시간이 상사의 상사에게 이야기하는 시간보다는 더 많아야 한다. 불편할 수도 있겠지만, 무엇에 대해서든 자신의 모든 상급자들에게 매달 조금씩 이야기를 할 의무가 있다고 생각한다.

여기에서 기본 법칙은, 우리에게 잠깐 이야기를 하는 사람은 모두 이득을 얻지만, 이야기가 길어질수록 그 이득은 적어진다는 것이다. 우리는 그들에게 이 이득을 나눠주고 그들과 대화하면서 이득을 받아와야 하며, 들인 시간과 이득 사이에 균형을 맞춰야 한다.

자신의 시간을 존중하는 것도 중요하다. 누군가에게 이야기하는 것을 통해, 그들의 시간을 쓰게 한다 해도, 자신의 시간을 많이 아낄 수 있다면, 그 사람의 시간이 나의 시간보다 더 소중하다고 생각하지 않는 한, 그것을 해야 한다. 우리 부족(tribe)에 대해서도 마찬가지이다.

특이한 사례로서 여름방학 인턴의 경우를 들 수 있다. 고급 기술직에 들어온 여름방학 인턴이 무슨 큰 일을 해 낼 것이라고 기대하기는 힘들다. 그들은 단지 거기 있는 모든 사람들을 괴롭게 할 것이라고 기대할 수 있을 것이다. 그렇다면 왜 이걸 참아야 하는가? 괴롭힘을 받는 사람은 그 인턴에게서 중요한 것을 얻게 될 것이기 때문이다. 그들은 살짝 자랑할 수 있는 기회가 생긴다. 새로운 아이디어를 들을 기회가 생길지도 모른다. 다른 시각에서 관찰해 볼 기회가 생긴다. 그 인턴을 채용하려는 의도가 있을 수도 있지만, 그렇지 않다 하더라도 얻는 것이 많다.

솔직히 누군가가 도움이 되는 말을 해 줄 수 있겠다는 생각이 들면, 그들에게 지혜와 판단력을 나눠 달라고 부탁해야 한다. 그러면 그 사람들은 뿌듯해 할 것이며, 나는 그들에게 무엇인가를 배우고 또 무엇인가를 가르쳐 주게 될 것이다. 좋은 프로그래머에게 영업 부사장의 조언은 별로 필요가 없겠지만, 만약 필요하게 된다면 반드시 조언을 구해야 한다. 나는 우리 영업부 직원들이 하는 일을 더 잘 이해하기 위해서 영업 관련 전화 통화 내용 몇 건을 같이 듣게 해 달라고 요청한 적도 있다. 30분도 채 안 걸린 일이었지만 그런 작은 노력이 영업부에 좋은 인상을 주었다고 생각한다.

2.2.5 현명하게 문서화하는 방법

아무도 읽지 않을 쓰레기 같은 글을 쓰기에는 인생은 너무 짧다. 쓰레기 같은 글은 아무도 읽지 않을 것이다. 따라서 문서화를 조금이라도 잘 하는 것이 최선이다. 관리책임자들은 이것을 이해하지 못하는 경우가 많다. 질이 떨어지는 문서를 보면서도 그들이 프로그래머들에게 의존하지 않고 있다는 헛된 안도감을 갖게 하기 때문이다. 누가 나에게 내가 작성한 문서가 정말 쓸모없다고 단호하게 말한다면, 그 말을 인정하고 조용히 다른 직업을 찾아보는 게 좋을 것이다.

문서를 잘 만들어내는 데 걸리는 시간의 양을 정확히 추정하여 문서화에 대한 요구를 완화시키는 데 드는 시간을 추정하는 데 이용하는 것만큼 효과적인 일은 없을 것이다. 진실은 냉혹하다. 문서화는 제품 검사처럼 코드 개발보다 더 긴 시간이 걸릴 수 있다.

문서화를 잘 하기 위해서는 우선 작문 실력이 있어야 한다. 작문에 대한 책을 찾아서 공부하고 실습해 보기를 권한다. 글이 깔끔하지 못하고 문서에 써야 하는 언어를 잘 모른다 해도, 다음의 황금률을 따르면 된다. "무엇이든지 남에게 대접을 받고자 하는 대로 너희도 남을 대접하라." 시간 여유를 가지고서 이 문서를 읽을 사람이 누구이며, 그 사람이 무엇을 얻고 싶어 할지, 그리고 그것을 어떻게 가르쳐 줄 수 있을지 진지하게 생각해 보라. 그렇게만 해도 평균 수준 이상의 문서는 만들어낼 수 있을 것이며 프로그래머로서도 훌륭하게 될 것이다.

코드 자체에 대해 문서화를 하게 될 때에는, 프로그래머가 아닌 사람들이 주로 읽을 문서를 작성할 때와는 반대로, 내가 아는 최고의 프로그래머들이 모두 공감하고 있는 바와 같이, 보면 바로 알 수 있게 코드를 작성하고, 코드만으로 그 의미가 분명하지 않은 곳에만 코드에 대해 문서화하라. 이렇게 하는 것이 좋은 두 가지 이유가 있다. 첫째로, 코드 수준의 문서를 봐야 하는 사람이라면 대부분의 경우에 어떻게든 그 코드를 읽을 수 있으며 그것을 선호하기 때문이다. 물론, 이것은 초보보다는 경력 있는 프로그래머에게 더 적당한 말이다. 하지만 더 중요한 것은, 따로 문서화하지 않을 경우, 코드와 문서가 불일치할 리가 없을 것이라는 사실이다. 소스 코드는 아무리 잘못되어 봐야 틀리거나 혼돈스러울 뿐이다. 하지만 문서는 정확하게 쓰지 않는다면 거짓말을 할 수 있고 이것은 천 배나 더 나쁜 일이다.

책임감 있는 프로그래머는 이 사실을 가볍게 받아들이지 않을 것이다. 보면 바로 알 수 있는 코드를 어떻게 작성할 것인가? 그것이 과연 무슨 뜻인가? 바로 이런 뜻이다.
  • 누군가 읽을 것이라는 사실을 염두에 두고 코드를 작성하기
  • (앞에서 말한) 황금률을 적용하기
  • 복잡하지 않은 해법을 선택하기 (다른 해법이 용케 더 빠르다 해도)
  • 코드를 어지럽게 하는 사소한 최적화 시도는 포기하기
  • 코드를 읽을 사람을 생각하면서 그 사람이 쉽게 이해할 수 있게 하기 위해 시간을 할애하기
  • "foo", "bar", "doIt" 같은 함수명은 절대 쓰지 않기!

2.2.6 형편없는 코드를 가지고 작업하기

다른 사람이 작성한 질이 떨어지는 코드를 가지고 작업해야 하는 경우가 많다. 하지만, 신발을 신고 걸어 보기까지는 그 신발이 아주 형편없다고 생각하지는 말라. 그 사람이 일정의 압박에 맞추기 위해 어떤 일을 빨리 끝내도록 독촉을 받았을 수 있다. 어떻든 간에, 불분명한 코드를 가지고 작업을 하려면 그것을 이해해야 한다. 그 코드를 이해하자면 시간이 걸릴 것이고, 그 시간은 정해진 일정의 어디에선가 빼 와야 할 것이므로, 이 사실을 분명히 해야 한다. 소스 코드를 이해하기 위해서는 그것을 읽어 볼 수밖에 없다. 아마도 그것을 가지고 실험을 해 봐야 할 것이다.

비록 자기 자신만을 위한 것이라 해도, 코드에 대한 문서화 노력을 통해 생각해 보지 못했던 각도에서 그 코드를 생각해 볼 수 있으므로, 지금이 문서화하기 좋은 시간이며, 그 결과로 나온 문서는 유용할 것이다. 이렇게 하는 동안 그 코드의 일부나 전체를 재작성하려면 무엇이 필요할 것인지에 대해 생각해 보라. 그것을 재작성하는 것이 실제로 시간을 아끼는 일이 될 것인가? 코드를 재작성한다면 더 믿을 만하게 될 것인가? 이 때에는 자만심을 주의하도록 하라. 코드를 재작성한다면, 그것을 검사해야 하는 부담은 얼마나 될 것인가? 얻을 수 있는 이득과 비교해 볼 때 정말로 재검사할 필요가 있는 것인가?

자기가 작성하지 않은 코드에 대한 작업 시간을 추정할 때, 그 코드의 품질에 따라 문제나 예측불허의 것이 생길 위험 가능성에 대한 인식이 달라진다.

깔끔하지 못한 코드에는 프로그래머의 최고의 도구 두 가지, 즉 추상화와 캡슐화가 특히 잘 적용된다는 사실을 잘 기억해 두라. 코드의 큰 부분을 재설계할 수는 없겠지만, 어느 정도 추상화를 해 줄 수 있다면, 전체를 재작업하지 않고서도 좋은 설계가 주는 이득을 어느 정도는 얻을 수 있을 것이다. 특히 안 좋은 부분은 다른 부분에서 떼어 놓아 독립적으로 재설계할 수 있도록 해 볼 수도 있다.

2.2.7 소스 코드 제어 시스템을 이용하는 방법

소스 코드 제어 시스템은 프로젝트를 효과적으로 관리할 수 있게 해 준다. 이 시스템은 개인에게도 유용하고 그룹에게는 필수적이다. 이 시스템은 여러 버전의 모든 변경 사항을 추적하므로 어떤 코드도 없어지지 않으며 변경 사항들의 의미를 기록해 놓을 수 있다. 소스 코드 제어 시스템이 있으면 소스 코드의 일부를 버리거나 디버그 코드를 넣는 일을 자신 있게 할 수 있다. 변경한 코드는 팀과 공유하거나 배포할 공식 코드와 잘 분리되어 보존되기 때문이다.

나는 뒤늦게야 소스 코드 제어 시스템의 이득을 알게 되었지만, 이제는 혼자 하는 프로젝트라 해도 그것 없이는 살 수 없을 것 같다. 일반적으로 이 시스템은 동일한 코드를 놓고 팀으로 작업할 때 필요하다. 하지만 이 시스템은 또 다른 큰 장점이 있다. 즉 이것을 통해 소스 코드를 성장하는 유기체로 인식하게 해 준다는 점이다. 변경된 것마다 새 이름이나 번호를 붙여 새로운 개정판으로 표시하기 때문에, 소프트웨어가 눈에 보이게 점진적으로 향상되어 간다고 생각하게 되는 것이다. 이것은 특히 초보자들에게 유용하다고 생각된다.

소스 코드 관리 시스템을 잘 이용하는 방법 중 하나는, 항상 최신의 상태를 유지하면서 며칠 동안 가만히 있는 것이다. 며칠 안에 마무리할 수 없는 코드는 체크인 상태로 있지만, 그것은 활성화되지 않고 호출되지 않을 상태로 있거나, 그 자신에서 갈라져 나온 분기(branch) 위치에 있을 것이므로, 다른 누구에게 어떠한 문제도 일으키지 않을 것이다. 실수가 있는 코드를 올려서 팀 동료들의 작업을 더디게 하는 것은 심각한 오류이다. 이것은 언제나 금기 사항이다.

2.2.8 단위별 검사를 하는 방법

단위별 검사, 즉 코드로 만든 기능의 각 부분을 그것을 작성한 팀에서 검사하는 것은 코딩의 일부이지, 코딩과 다른 무엇이 아니다. 코드를 어떻게 검사할지 설계하는 것도 코드 설계의 한 부분이다. 비록 한 줄이라 해도 검사 계획을 기록해 놓아야 한다. 때때로 그 검사는 다음과 같이 단순할 것이다. "이 버튼이 좋아 보이는가?" 때로는 다음과 같이 복잡할 수도 있다. "이 정합(matching) 알고리듬은 틀림없이 정확한 짝을 찾아낼 것인가?"

할 수 있다면 검증 조건(assertion) 확인 방법이나 검사 자동화 도구(test driver)를 사용하라. 이 방법은 버그를 일찍 잡을 수 있게 해 줄 뿐만 아니라, 나중에도 유용하게 쓰일 수 있으며, 이렇게 하지 않았더라면 한참 고민하게 되었을 애매한 문제들을 줄일 수도 있을 것이다.

익스트림 프로그래밍(Extreme Programming) 기법을 사용하는 개발자들은 단위별 검사를 최대한 효과적으로 활용하여 코드를 작성한다. 이 작성 방법을 추천하는 것만큼 좋은 일도 없을 것이다.

2.2.9 막힐 때는 잠깐 쉬어라

막힐 때는 잠깐 쉬어라. 나는 막힐 때는 15분 정도 명상을 하곤 한다. 그러면 다시 문제로 돌아왔을 때 그것이 마술같이 해결되곤 한다. 규모가 클 경우에는 하룻밤 잘 자는 것이 같은 효과를 내기도 한다. 잠시 다른 활동을 하는 것이 효과적일 수도 있다.

2.2.10 집에 갈 시간을 인지하는 방법

컴퓨터 프로그래밍은 문화라고 할 만한 활동이다. 불행한 것은, 이것이 정신적, 신체적 건강을 그렇게 중요하게 생각하지 않는 문화라는 사실이다. 문화적이고 역사적인 이유 (예를 들어, 컴퓨터가 쉬는 밤중에 작업할 필요) 때문에, 그리고 저항할 수 없는 출시 일정의 압박과 프로그래머의 부족 때문에 컴퓨터 프로그래머는 전통적으로 초과 근무를 해 왔다. 소문으로 듣는 모든 이야기를 믿을 것이라고 생각하지는 않지만, 주당 60시간 근무는 일상적이며, 50시간은 상당히 적은 편에 속한다. 즉, 이보다 더 많은 시간이 요구되는 경우가 있다는 말이다. 이것은 좋은 프로그래머에게는 심각한 문제이다. 그는 자기 자신만 아니라 자기 팀 동료들도 책임지고 있기 때문이다. 자기가 집에 갈 시간, 때로는 다른 사람을 집에 보낼 시간도 인지하고 있어야 한다. 아이를 키우는 불변의 법칙이 없듯이, 이 문제를 해결할 불변의 법칙은 없다. 모든 사람은 서로 다르기 때문이다.

주당 60시간 이상 일하는 것은, 짧은 기간 (한 주 정도) 동안이나 해 볼 수 있을 정도로, 내게는 엄청난 노력이 필요하지만, 때로는 그렇게 해야 할 때가 있다. 한 사람에게 60시간 동안 일을 하게 하는 것이 공정한 것인지는 잘 모르겠다. 사실 40시간이 공정한 것인지도 잘 모르겠다. 하지만 분명한 것은, 초과 근무하는 시간 동안 별로 얻는 것 없이 오래 일하기만 하는 것은 어리석은 일이라는 사실이다. 나에 대해 말하자면, 주당 60시간 이상 일하는 것이 그렇다. 개인적으로는, 프로그래머는 고귀한 의무(noblesse oblige)를 다해야 하고 무거운 짐을 져야 한다고 생각한다. 하지만 봉이 되는 것은 프로그래머의 의무가 아니다. 그런데 슬프게도 프로그래머들은, 경영진의 눈에 들기 위해 애쓰는 관리책임자 같은 이들을 위해 재주를 부리는 곰이 되는 경우가 있다. 프로그래머들은, 다른 사람들을 기쁘게 하고 싶고, 싫다는 말을 잘 못하기 때문에, 종종 이런 요구에 굴복한다. 이를 대처하기 위한 네 가지 방어법이 있다.
  • 회사의 모든 사람들과 최대한 많이 대화하여 아무도 무슨 일이 일어나고 있는지에 대해 경영진을 현혹하지 못하게 하라.
  • 시간을 추정하고 일정을 잡을 때 방어적이고 명백하게 하여, 모든 사람들이 일정이 어떻게 되고 현재 어디쯤 가고 있는지 잘 볼 수 있게 하라.
  • 요구를 거부하는 법을 배우고, 필요하다면 팀이 함께 거부하도록 하라.
  • 어쩔 수 없다면 회사를 그만두라.

많은 프로그래머들이 좋은 프로그래머이고, 좋은 프로그래머는 많은 것을 이루길 원한다. 이를 위해 그들은 시간을 효과적으로 관리해야 한다. 어떤 문제에 대해 생각을 가다듬고 거기에 깊이 몰두하게 될 때까지는 상당한 수고가 필요하다. 많은 프로그래머들은 생각을 가다듬고 몰두할 수 있는, 방해받지 않는 긴 시간이 있을 때 가장 잘 일할 수 있다고 한다. 하지만 사람들은 잠도 자고 다른 의무들도 이행해야 한다. 모든 사람들은 자신의 인간적 리듬과 업무적 리듬을 모두 만족시키는 방법을 찾아야 한다. 모든 프로그래머는, 아주 중요한 회의에만 참석하고 나머지는 일에 집중하는 날들을 확보하는 것과 같이, 효율적인 업무 기간을 획득하기 위해 최선을 다해야 한다.

나는 아이들이 있기 때문에 가끔이라도 아이들과 저녁 시간을 보내기 위해 노력한다. 나에게 가장 잘 맞는 리듬은, 하루 날 잡아 오래 일하고, 사무실이나 사무실 부근에서 잠을 잔 다음 (나는 집에서 직장까지 통근 시간이 길다), 일찍 집에 가서 아이들이 잠자리에 들기 전까지 시간을 보내는 것이다. 이것이 편안하지는 않지만, 여태껏 시험해 본 최선의 타협점이었다. 전염성 있는 병에 걸렸다면 집에 가라. 죽고 싶다는 생각이 든다면 집에 가야 한다. 몇 초 이상 누군가를 죽이고 싶다는 생각이 든다면 집에 가서 쉬어야 한다. 누군가가 가벼운 우울증을 넘어서 심각한 정신 이상이나 정신병의 증세를 보인다면 집에 가게 해야 한다. 피로 때문에 평소와 달리 부정직하거나 남을 속이고 싶다는 유혹이 든다면 쉬어야 한다. 피로와 싸우기 위해 마약이나 각성제를 쓰지 말라. 카페인을 남용하지도 말라.

2.2.11 까다로운 사람들과 상대하는 방법

까다로운 사람들과 상대해야 하는 일이 있을 것이다. 자기 자신이 까다로운 사람일 수도 있다. 나 자신이 같이 일하는 사람들이나 권위 있는 인물들과 수시로 충돌하는 유형의 사람이라면, 여기에서 볼 수 있는 독립심은 소중하게 여겨야 할 것이나, 자신의 지성이나 원칙들을 희생하지 않는 범위 내에서 인간관계의 기능도 길러야 할 것이다.

이런 종류의 일을 겪어 보지 않았거나, 지금까지 살면서 직장 생활에는 별로 쓸모없는 행동 양식만 익혀 온 프로그래머들에게는 이것이 짜증스러운 일이 될 수 있다. 까다로운 사람들은 대개 반대 의견에 단련되어 있고 다른 사람들과 타협해야 한다는 사회적 압력에 별로 영향을 받지 않는다. 이 때 열쇠는 그 사람들을 적당히 존중해 주는 것이다. 이것은 내가 하고 싶은 것 이상으로 해 주는 것이지만, 그 사람들이 원하는 만큼은 안 될 것이다.

프로그래머들은 팀으로 함께 일해야 한다. 의견 불일치가 생기면, 어떻게든 해결해야 한다. 무작정 피할 수는 없는 노릇이다. 까다로운 사람들은 종종 매우 똑똑하고 쓸 만한 이야기를 하기도 한다. 까다로운 사람에게 편견 없이 귀를 기울이고 이해해 주는 것은 매우 중요하다. 대화 단절은 의견 불일치의 기반이 되지만, 강한 인내심으로 극복할 수 있는 경우도 있다. 대화가 산뜻하고 정감 있게 이뤄지도록 노력하고, 의견 충돌을 일으킬 만한 논쟁에 말려들지 말라. 이해하려고 노력할 만큼 한 뒤에는 결단을 내리라.

으스대는 사람의 강요 때문에 동의하지도 않는 일을 하지는 말라. 자신이 팀장이라면 최선이라고 생각하는 일을 하라. 개인적 이유로 결정을 내리지 말고, 자기가 결정한 근거를 설명할 준비를 해 두라. 까다로운 사람이 팀장이라면, 그의 결정이 개인적으로 영향을 미치지 않도록 하라. 일이 자기 방식대로 진행되지 않아도, 그 다른 방식에 따라 마음을 다해 일하라.

까다로운 사람들도 변하며 나아지기도 한다. 내 눈으로 직접 본 적도 있지만, 그렇게 흔하지는 않다. 어쨌든 모든 사람은 수시로 오르락내리락하기 마련이다.

모든 프로그래머, 특히 팀장들이 대면해야 하는 도전들 중 하나는 까다로운 사람을 전적으로 몰두하게 하는 것이다. 그런 사람들은 다른 사람에 비해서 일의 책임을 피하거나 수동적으로 저항하는 경향이 크다.

3 중급자

3.1 개인적 기능들

3.1.1 의욕을 계속 유지하는 방법

프로그래머들이 아름답고 유용하고 멋진 것을 만들고 싶어 하는 의욕이 매우 크다는 사실은 훌륭하고도 놀라운 일이다. 이러한 욕구는 프로그래머에게만 있는 것도 아니고 보편적인 것도 아니지만, 이것은 프로그래머들 사이에 매우 강하고 일반적이어서 다른 일을 하는 사람들과 구분이 된다.

이것은 실제로 중요한 결과를 낳는다. 프로그래머에게 아름답지도 유용하지도 멋지지도 않은 일을 시키면 그들은 사기가 떨어진다. 너저분하고 멍청하고 지루한 일을 해서 돈을 많이 벌기도 한다. 하지만 결국에는 재미있게 하는 일이 회사에 큰 돈을 벌어다 준다.

분명히 의욕을 불러일으키는 기법들을 중심으로 조직된 모든 산업 분야에서 여기에 적용되는 것들이 있다. 프로그래밍에 해당한다고 인정할 만한 것들은 다음과 같다.
  • 그 일을 자랑스럽게 이야기한다.
  • 새로운 기법, 언어, 기술을 적용할 기회를 찾는다.
  • 각 프로젝트에서 아무리 작더라도 무엇인가를 배우거나 가르치려고 노력한다.

끝으로, 할 수 있다면, 개인적으로 의욕을 얼마나 고취시키는가에 따라 자신의 일의 영향력을 가늠해 보라. 예를 들어, 버그를 고칠 때, 내가 버그를 몇 개나 고쳤는지 세는 것으로 의욕이 생기지는 않는다. 지금까지 고친 버그의 개수가 아직 남아 있는 버그의 개수와 무관하며, 그 개수는 회사의 고객들에게 기여하는 전체 가치에서 아주 사소한 부분을 차지할 뿐이기 때문이다. 하지만, 버그 하나마다 고객 한 사람이 기뻐한다고 생각하면 그것은 개인적으로 의욕이 생기게 한다.

3.1.2 널리 신뢰받는 방법

신뢰받기 위해서는 신뢰받을 만해야 한다. 또한 활동이 두드러져야 한다. 자신에 대해 아무도 알지 못한다면, 아무런 신뢰도 받을 수 없을 것이다. 팀 동료처럼 자신과 가까운 사람들과 같이 있을 때는 이것이 큰 문제가 아닐 것이다. 신뢰는 자기 부서나 팀이 아닌 사람들에게 응답하고 지식을 줌으로써 쌓아간다. 때로는 이러한 신뢰를 악용하여 불합리한 부탁을 하는 사람도 있다. 이럴 때에는 걱정하지 말고, 그 부탁을 들어주자면 자신이 무슨 일을 포기해야 하는지 설명하면 된다.

모르는 것을 아는 체하지 말라. 팀 동료가 아닌 사람들에게는 "머리에서 맴돌면서 기억나지 않는 것"과 "전혀 알 수 없는 것"을 명확히 구분해야 할 것이다.

3.1.3 시간과 공간 사이에서 균형을 잡는 방법

대학에 가지 않아도 좋은 프로그래머는 될 수 있지만, 기초적인 계산 복잡도 이론을 모른다면 좋은 중급 프로그래머는 될 수 없다. O("big O") 표기법을 알 필요는 없지만, "상수 시간", "n log n", "n 제곱"의 차이는 이해할 수 있어야 한다. 이런 지식이 없어도 시간과 공간 사이에서 균형을 잡는 방법을 직관으로 알고 있을 수 있지만, 그런 지식이 없다는 것은 동료들과 대화할 때 필요한 튼튼한 기초가 없는 것과 같다.

알고리듬을 설계하거나 이해할 때, 그것을 실행하는 데 걸리는 시간은 입력 값의 크기의 함수인 경우가 있다. 이 때, 알고리듬의 실행 시간이 (변수 n으로 표현되는) 그 크기와 그 크기의 로그값의 곱에 비례하면, 그 최악(또는 기대되는, 또는 최선)의 실행 시간이 "n log n"이라고 말할 수 있다. 이 표기법과 말하는 방식은 자료 구조가 차지하는 공간에도 마찬가지로 적용될 수 있다.

내게는, 계산 복잡도 이론이 물리학만큼이나 아름답고 심오하게 보인다. (조금 딴 길로 샌 것 같다!)

시간(프로세서 속도)과 공간(메모리)은 서로 균형을 맞출 수 있다. 공학이란 타협에 대한 것이며, 이것은 아주 좋은 예가 된다. 이것은 항상 체계적인 것은 아니다. 일반적으로 꽉 조이는 인코딩을 통해 공간을 절약할 수 있지만, 그것을 디코딩해야 할 때는 계산 시간이 더 많이 걸릴 것이다. 캐쉬를 사용함으로써, 즉 가까이에 복사본을 저장할 공간을 사용함으로써 시간을 절약할 수 있지만, 캐쉬의 내용을 일관되게 유지하고 있어야 할 것이다. 자료 구조에 더 많은 정보를 담음으로써 시간을 절약할 수 있는 경우가 있다. 이것은 대개 적은 공간을 차지하지만 알고리듬이 복잡해질 수 있다.

공간이나 시간의 균형 관계를 개선하는 것은 종종 다른 쪽이 극적으로 변하게 할 수 있다. 하지만 이 작업을 하기 전에 지금 개선하려고 하는 것이 정말로 그런 개선이 필요한 것인지 스스로 물어야 한다. 알고리듬을 가지고 작업하는 것은 재미있는 일이지만, 아무 문제가 없는 것을 개선하려는 것은 눈에 띌 만한 차이는 못 내면서 검사할 짐만 늘어나게 할 것이라는 냉엄한 사실에 대해 눈이 가려지지 않도록 조심해야 한다.

최신 컴퓨터의 메모리는, 프로세서 시간과 달리 벽에 부딪히기 전까지는 그것이 어떻게 쓰이는지 볼 수 없기 때문에, 값싼 것처럼 보인다. 하지만 문제가 생기기라도 하면 그것은 재앙이 된다. 메모리를 사용하는 데에는, 메모리에 상주해야 하는 다른 프로그램에 미치는 영향이나, 메모리를 할당하고 해제하는 데 드는 시간 등의 숨어 있는 비용이 있다. 속도를 얻으려고 공간을 써 버리기 전에 이 사실을 주의 깊게 생각하라.

3.1.4 압박 검사를 하는 방법

압박 검사(stress test)는 재미있다. 처음에는 압박 검사의 목적이 시스템에 부하가 걸려도 잘 동작하는지 알아보는 것으로 보인다. 실제로는 시스템에 부하가 걸려도 잘 동작하지만 부하가 아주 클 때는 어떤 방식으로든 동작이 멈추는 경우가 대부분이다. 나는 이것을 벽에 부딪침 또는 탈진이라고 부른다. 예외가 있기도 하지만, 거의 항상 '벽'은 있다. 압박 검사의 목적은 그 벽이 어디에 있는지 알아보는 것이며, 그 벽을 얼마나 더 밀어낼 수 있을지 알아보는 것이다.

압박 검사 계획은, 그것을 통해 프로젝트에서 기대하는 바가 무엇인지 명확해지기 때문에, 프로젝트 초기에 세워야 한다. 웹 페이지 요청에 2초가 걸리는 것은 비참한 실패일까, 대단한 성공일까? 동시 사용자 500명은 충분한가? 이것은 물론 상황에 따라 다르지만, 그 요구에 부응하는 시스템을 설계할 때 그 답을 알고 있어야 한다. 압박 검사는 실제 상황을 충분히 쓸 만하게 본떠서 해야 한다. 동시에 시스템을 사용하면서 오류를 일으키거나 무엇을 할지 예측할 수 없는 500명의 사람들을 쉽게 흉내 내는 일이 실제로는 불가능하지만, 500개의 모의실험을 만들어 사람들이 어떻게 행동할지 본뜨게 해 보는 정도는 할 수 있을 것이다.

압박 검사를 할 때는 가벼운 부하에서 시작해서, 입력 비율이나 입력 크기와 같은 어떤 범위에 따라 벽에 부딪칠 때까지 부하를 늘려간다. 벽이 요구 조건을 만족하기에 너무 가깝다면, 어떤 자원이 병목이 되어 있는지 알아내라. (대개 주된 원인이 있기 마련이다.) 그것이 무엇 때문인가? 메모리? 프로세서? I/O? 네트워크 대역폭? 데이터 쟁탈? 그 후에는 그 벽을 어떻게 움직일 수 있을지 알아내라. 벽을 움직이는 것, 즉 시스템이 견딜 수 있는 최대 부하를 증가시키는 것이 부하가 적은 시스템의 성능에 도움이 되지 않거나 오히려 해가 될 수도 있다는 사실은 기억하라. 보통은 큰 부하가 걸렸을 때의 성능이 적은 부하가 걸렸을 때의 성능보다 중요하다.

머리 속으로 모형을 그릴 수 있도록 여러 가지 다른 차원들도 살필 수 있어야 한다. 한 가지 기법만으로는 충분하지 않다. 예를 들어, 로그 기록은 시스템에서 일어난 두 사건 사이의 벽시계 시간에 대해 잘 알 수 있게 해 주지만, 그것이 잘 구성되어 있지 않은 한, 메모리 사용이나 자료 구조의 크기에 대해서는 살펴볼 수 없다. 같은 원리로, 현대적인 시스템에서는 많은 컴퓨터와 많은 소프트웨어 시스템들이 서로 협력하여 동작하는데, 특히 벽에 부딪쳤을 때 (즉, 성능이 입력 크기에 비례하지 않게 될 때) 이 다른 소프트웨어 시스템들이 병목이 될 수 있다. 이 시스템들을 살펴볼 수 있는 것은, 모든 관련 장비들의 프로세서 부하만 측정할 수 있다 해도, 큰 도움이 될 수 있을 것이다.

벽이 어디에 있는지 아는 것은, 벽을 옮기는 것뿐만 아니라 사업을 효과적으로 관리할 수 있도록 예측 가능성을 마련해 주는 데에도 필수적이다.

3.1.5 간결성과 추상성의 균형을 잡는 방법

추상성은 프로그래밍의 열쇠이다. 얼마나 추상화해야 할지는 주의해서 선택해야 한다. 초보 프로그래머들은 열심이 지나쳐서 실제로 필요 이상으로 추상화하는 경우가 있다. 이것의 징조 중 하나는 아무 코드도 들어 있지 않으면서, 다른 것을 추상화하는 것 외에 아무 일도 하지 않는 클래스를 만드는 것이다. 이런 것의 매력은 이해할 만하지만 코드 간결성의 가치도 추상성의 가치에 비교하여 재 봐야 한다. 때때로 열정적인 이상주의자들이 저지르는 실수를 보게 된다. 즉 프로젝트 초기에 수많은 클래스들을 정의하면서 그것을 통해 추상성을 멋지게 달성하고, 발생할 수 있는 모든 사태를 다룰 수 있을 것으로 기대한다. 그런데 프로젝트가 진행되고 피로가 쌓임에 따라 코드 자체가 너저분해진다. 함수의 몸체는 되어야 할 것 이상으로 길어진다. 비어 있는 클래스들은 문서화하는 데 짐이 되고 압박이 오면 결국 잊혀진다. 추상화에 쏟은 에너지가 프로그램을 간결하고 단순하게 하는 데에 쓰였다면 최종 결과가 더 좋아졌을지도 모른다. 이것은 사색적인(speculative) 프로그래밍의 전형이다. 나는 폴 그레이엄(Paul Graham)의 "간결함이 힘이다(Succinctness is Power)"라는 글을 강력 추천한다. <PGSite>

정보 은닉이나 객체 지향 프로그래밍과 같은 유용한 기법들을 지나치게 사용하면서 독단적 견해가 생기기도 한다. 이 기법들은 코드를 추상화하게 하고 변화를 예측하게 한다. 하지만 개인적인 생각으로는 너무 사색적인 코드를 작성하지 않는 것이 좋다. 예를 들어, 어떤 객체의 정수 변수를 변경자(mutator)와 접근자(accessor) 뒤에 숨겨서 변수 자체는 드러나지 않고 작은 접점(interface)만 드러나게 하는 것은 받아들일 만한 스타일이다. 이를 통해서, 호출하는 코드에 영향을 주지 않으면서 그 변수의 구현 방식을 바꿀 수 있으며, 이것은 매우 안정된 API를 내놓아야 하는 라이브러리 제작자에게 적합할 것이다. 하지만 나는, 호출하는 코드를 우리 팀이 소유하고 있어서 호출되는 쪽만큼 호출하는 쪽도 다시 코딩할 수 있다면, 코드가 장황해지는 것을 감수할 만큼 이것이 이득이 된다고 생각하지는 않는다. 이런 사색적 프로그래밍으로 얻는 이득을 따져 보면 코드 너덧 줄 늘어나는 것도 아깝다.

이식성(portability)에 대해서도 비슷한 문제를 볼 수 있다. 모든 코드는 다른 컴퓨터, 컴파일러, 소프트웨어 시스템, 플랫폼에 바로 혹은 아주 쉽게 이식될 수 있어야 하는가? 나는 이식성은 없지만 간결하면서 쉽게 이식할 수 있는 코드가, 장황하면서 이식성 있는 것보다 낫다고 생각한다. 특정 DBMS에 맞는 데이터베이스 질의를 하는 클래스와 같이 이식성 없는 코드를 정해진 영역에 한정해 놓는 것도 비교적 쉬우면서 분명히 좋은 생각이다.

3.1.6 새로운 기능을 배우는 방법

새로운 기능, 특히 기술과 무관한 것을 배우는 것은 무엇보다 재미있는 일이다. 이것이 프로그래머들의 의욕을 얼마나 많이 불러일으키는지 이해하는 회사들은 사기가 더욱 높아질 수 있을 것이다.

인간은 행함으로 배운다. 책 읽기와 수업 듣기가 유용한 것은 틀림없지만, 아무 프로그램도 작성해 보지 않은 프로그래머를 인정할 수 있겠는가? 어떤 기능이든 배우기 위해서는 그 기능을 스스럼없이 연습해 볼 수 있어야 할 것이다. 새로운 프로그래밍 언어를 배울 때에는, 큰 프로젝트를 해야 하게 되기 전에 그 언어를 사용하는 작은 프로젝트를 해 보라. 소프트웨어 프로젝트 관리를 배울 때에는, 작은 프로젝트를 먼저 관리해 보도록 하라.

좋은 사수(mentor)가 있다고 해서 스스로 하는 것을 소홀히 해서는 안 되겠지만, 사실 책 한 권보다는 훨씬 낫다. 사수의 지식을 전수받는 대신 그들에게 무엇을 해 줄 수 있을까? 최소한 그들의 시간이 낭비가 되지 않도록 열심히 공부해 줄 수는 있을 것이다.

상사에게 공식 연수를 받게 해 달라고 요청해 보라. 하지만 이것이 그 시간 동안 배우고 싶은 새로운 기술을 가지고 놀아 보는 것보다 별로 나을 것이 없는 경우도 있다는 것을 알아 두라. 그렇다 해도, 우리의 불완전한 세상에서는 공식 연수에서 저녁 식사 파티를 기다리며 강의 내내 잠만 자는 경우가 많음에도 불구하고 노는 시간보다는 연수를 요청하기가 쉬울 것이다.

팀장이라면, 팀원들이 어떻게 배우는지 이해하고 그들에게 관심 있는 기능을 연습할 수 있는 적절한 규모의 프로젝트를 할당함으로써 그들을 도와주라. 프로그래머에게 가장 중요한 기능은 기술에 관한 것이 아니라는 사실을 잊지 말라. 팀원들에게 용기와 정직성과 의사소통 능력을 시험하고 실행할 수 있는 기회를 주라.

3.1.7 타자 연습

자판을 보지 않고 타자할 수 있도록 연습하라. 이것은 중급 기능이다. 왜냐하면, 코드 작성은 어려운 일이므로, 아무리 타자를 잘 한다 해도, 거기에는 타자 속도가 별 의미가 없고, 코드 작성에 걸리는 시간에 별 영향을 주지도 않기 때문이다. 하지만, 중급 프로그래머가 되면 동료나 다른 사람들에게 일반 언어로 글을 쓰는 데 많은 시간을 보내게 될 것이다. 이것은 자신의 헌신성에 대한 재미있는 시험이다. 그런 것을 배우는 일이 그렇게 즐겁지는 않지만 어쨌든 시간을 바쳐야 한다. 마이클 티먼(Michael Tiemann)이 MCC에 있을 때 사람들이 그의 방문 밖에 서서, 엄청나게 빠른 타자 속도 때문에 자판이 윙윙거리는 소리를 듣곤 했다는 전설이 있다. 주: 지금 현재 그는 레드햇(RedHat)의 최고 기술 책임자(CTO)이다.

3.1.8 통합 검사를 하는 방법

통합 검사(integration testing)는 단위별 검사를 마친 여러 구성요소들을 통합하는 검사이다. 통합은 비싼 대가를 치르고 얻게 되며, 그것은 이 검사를 통과함으로써 얻게 된다. 시간 추정과 일정 계획에 이 검사를 포함시켜야 한다.

이상적으로는 마지막 단계에서 별도로 통합을 실시하지 않아도 되도록 프로젝트를 조직해야 할 것이다. 프로젝트가 진행되는 과정에서 각 요소들이 완성되어 가면서 점진적으로 그것들을 통합하는 것이 훨씬 낫다. 별도의 통합 과정을 피할 수 없다면 주의해서 시간을 추정하라.

3.1.9 의사소통을 위한 용어들

프로그래밍 언어는 아니지만 의사소통을 위해 공식적으로 정의된 문법 체계에 따른 용어들이 있다. 이 용어들은 특별히 표준화를 통해 의사소통을 돕기 위해 만들어진 것이다. 2003년 현재, 이런 용어들 중 가장 중요한 것으로 UML, XML, SQL이 있다. 이것 모두에 대해서는, 제대로 의사소통하고 언제 그 용어를 사용할지 결정할 수 있기 위해, 어느 정도 친숙해질 필요가 있다.

UML은 설계에 대해 설명하는 도형을 그리기 위해 풍부하게 갖춰진 형식 체계이다. 시각적이고 형식적이라는 점에서 일종의 미학(beauty lines)이라고 할 수 있다. 저자와 독자가 모두 UML을 안다면 많은 양의 정보를 쉽게 전달할 수 있다. 설계 내용에 대해 의사소통할 때 UML을 사용하는 경우가 있으므로 그것을 알 필요가 있다. UML 도형을 전문적으로 그려 주는 유용한 도구들이 있다. 많은 경우에 UML는 너무 형식적이어서, 나는 설계 내용을 도형으로 나타내기 위해 단순한 상자와 화살표 형식을 사용하곤 한다. 하지만 나도 UML이 최소한 라틴어를 공부하는 것만큼 도움이 된다는 사실은 어느 정도 확신한다.

XML은 새로운 표준을 정의하기 위한 표준이다. 간혹 XML이 데이터의 상호교환 문제에 대한 해결책인 것처럼 소개되기도 하지만 사실 그런 것은 아니다. 그것보다는, 데이터의 상호교환에서 가장 따분한 부분, 즉 특정 방식으로 표현된 데이터를 선형의 구조로 나열하고, 그것을 다시 원래의 구조로 파싱하는 작업을 기특하게도 자동화하는 것이다. XML은, 비록 아직 필요한 것들의 일부만 구현되긴 했어도, 훌륭하게 자료형 검사와 정확성 검사를 한다.

SQL은, 완전한 프로그래밍 언어는 아니지만, 매우 강력하고 풍부한 데이터 질의와 처리 언어이다. 이것은 다양한 종류가 있으며, 특히 제품에 크게 의존하지만 표준화된 핵심 부분보다는 덜 중요하다. SQL은 관계형 데이터베이스의 공통 언어이다. 관계형 데이터베이스를 이해하는 것이 득이 되는 분야에서 일할 수도 있고 그렇지 않을 수도 있지만, SQL과 그 문법에 대해 기초적인 것은 알아둘 필요가 있다.

3.2 팀의 기능들

3.2.1 개발 시간을 관리하는 방법

개발 시간을 관리하기 위해서는, 프로젝트 계획서가 간결하고 신선(up-to-date)하도록 하라. 프로젝트 계획서에는 시간 추정, 작업 일정, 진척 상황을 표시하기 위한 진도표(milestones), 시간 추정된 각 과제에 대한 팀이나 자기 자신의 시간 할당 등이 들어 있다. 여기에는 품질보증팀 사람들과 회의, 문서 준비, 장비 주문과 같이 잊지 말고 해야 할 다른 일들도 포함된다. 팀으로 일하는 것이라면, 프로젝트 계획서는 프로젝트 시작부터 진행 과정 내내 팀 전체의 동의에 의한 것이어야 한다.

프로젝트 계획서는 의사결정을 돕기 위해 존재하는 것이지, 자신이 얼마나 조직적인 사람인지 보여 주기 위한 것이 아니다. 프로젝트 계획서가 너무 길고 오래되었다면 의사결정에 아무 소용이 없을 것이다. 실제로 이러한 의사결정은 개개인에 관한 것이다. 계획서와 판단력에 의해 과제를 이 사람에게서 저 사람에게로 넘길 것인지 결정해야 한다. 진도표는 진척 상황을 표시한다. 화려한 프로젝트 기획 도구를 사용한다면 그것으로 프로젝트에서 '개발 초기 대형 설계(Big Design Up Front)'를 하려는 유혹에 빠지지 말고, 간결함과 신선함을 위해 사용하도록 하라.

진도를 못 맞췄다면, 그 진도만큼 프로젝트 일정 완료가 늦어진다고 상사에게 알리는 등의 즉각적인 행동을 취해야 한다. 시간 추정과 작업 일정은 처음부터 완벽할 수는 없을 것이다. 이것은 진도를 못 맞춘 날들을 프로젝트 후반에 만회할 수 있을지도 모른다는 환상을 낳는다. 물론 그럴 수 있을지도 모른다. 하지만 이것은 그 부분을, 과대 추정할 수도 있었겠지만, 과소 추정했기 때문이다. 그러므로 좋듯 싫든 간에 프로젝트의 일정 완료는 이미 늦어진 것이다.

계획서에 다음의 시간들도 포함되도록 명심하라. 팀 내부 회의, 시연, 문서화, 일정에 따라 반복되는 활동들, 통합 검사, 외부인들 상대하기, 질병, 휴가, 기존 제품들의 유지, 개발 환경의 유지 등. 프로젝트 계획서는 외부인이나 상사에게 자신이나 자신의 팀이 무엇을 하고 있는지 보여 줄 수 있는 '중간' 다리가 될 수 있다. 이런 이유 때문에 계획서는 짧고 신선해야 한다.

3.2.2 타사 소프트웨어의 위험 부담을 관리하는 방법

프로젝트 내에서 통제할 수 없는 다른 조직이 만든 소프트웨어에 의존해야 하는 경우가 있다. 타사 소프트웨어와 연관된 큰 위험 부담들은 관련된 모든 사람이 인식하고 있어야 한다.

절대, 절대로 거품(vapor)에 희망을 두지 말라. 거품이란, 나올 것이라고 광고는 하면서 아직 나오지 않은 소프트웨어를 말한다. 이것은 업계를 떠나게 될 가장 확실한 방법이다. 어떤 특징이 있는 어떤 제품이 어느 날에 출시될 것이라는 소프트웨어 회사의 약속을 의심스러워하기만 하는 것은 현명하지 않다. 그것을 완전히 무시하고 그 소식을 들었다는 것조차 잊어버리는 것이 더욱 현명하다. 회사에서 쓰이는 어떤 문서에도 그 소프트웨어에 대해 쓰지 않도록 하라.

타사 소프트웨어가 거품이 아니라면, 위험 부담은 여전히 있지만 그래도 최소한 손을 써 볼 수는 있을 것이다. 타사 소프트웨어의 사용을 고려하고 있다면, 일찍 에너지를 들여서 그것을 평가해야 한다. 사람들은 세 가지 제품이 적합한지 평가하는 데에 2주에서 2개월이 걸린다는 말을 듣고 싶어 하지 않겠지만, 최대한 일찍 그 일을 마쳐야 한다. 적절한 평가 없이는 통합(integration)에 드는 비용을 정확하게 추정할 수 없다.

특정한 목적에 기존의 타사 소프트웨어가 적합한지는 극소수의 사람들만 알고 있다. 그것은 매우 주관적이며 일반적으로 전문가들의 영역이다. 이런 전문가들을 찾을 수 있다면 많은 시간을 아낄 수 있다. 프로젝트가 타사 소프트웨어 시스템에 너무 완전히 의존해서 통합에 실패하면 프로젝트 자체가 실패하는 경우도 있을 수 있다. 그러한 위험 부담들을 작업 일정에 분명히 밝혀 두라. 쓸 수 있는 다른 시스템을 확보하는 등 만약의 사태에 대비한 계획도 세워 두고, 위험 부담을 일찍 제거할 수 없을 때 스스로 그 기능을 구현할 수 있는 능력도 키워 두라. 절대로 일정이 거품에 의존하지 않도록 하라.

3.2.3 컨설턴트를 관리하는 방법

컨설턴트를 활용하되 그들에게 의지하지는 말라. 그들은 훌륭하고 많이 존중받을 만하다. 그들은 수많은 다양한 프로젝트들을 보아 왔기 때문에 특정한 기술들이나 프로그래밍 기법들에 대해 많이 알고 있는 경우가 있다. 그들을 활용하는 최선의 방법은, 사례를 중심으로 강의하는 사내 강사로서 활용하는 것이다.

하지만, 그들의 강점과 약점을 알 만한 충분한 시간이 없다고 해서 그들을 일반 직원들처럼 팀의 일원이 되게 할 수는 없다. 그들이 금전적 손실을 감수하는(financial commitment) 경우는 거의 없다. 그들은 아주 쉽게 떠난다. 회사가 잘 운영된다면 그들이 얻을 것은 별로 없을 것이다. 어떤 사람은 좋고, 어떤 사람은 그저 그렇고, 어떤 사람은 안 좋을 것이다. 컨설턴트를 선정할 때도 직원을 채용할 때처럼 주의를 기울이지 않는다면 좋은 사람을 만나기는 힘들 것이다.

컨설턴트가 코드를 작성해야 한다면, 계속해서 그 코드를 주의를 기울여 검토해야 한다. 검토해 보지 않은 코드가 뭉텅이로 있는 위험 부담을 안고서 프로젝트를 잘 마칠 수는 없을 것이다. 이것은 사실 모든 팀원들에게도 적용되는 사실이지만, 가까이에 있는 팀원들이야 잘 아는 사람들 아닌가.

3.2.4 딱 적당하게 회의하는 방법

회의에 드는 비용을 잘 고려하라. 그 비용은 회의 시간에 참석 인원수를 곱한 값이다. 회의가 필요할 때도 있지만 규모가 작을수록 좋다. 의사소통의 질은 소규모 회의 때 더 좋고, 낭비되는 총 시간도 적다. 누가 회의 때 지루해하는 것 같으면, 이것은 회의 참석 인원을 줄여야 한다는 신호이다.

비공식적인 의사소통을 장려하기 위해서는 할 수 있는 일은 모두 해야 한다. 다른 시간들보다 동료들과 함께 하는 점심시간 동안 쓸만한 일들이 더 많이 이뤄진다. 그런데 이것을 인식하지도 않고 이 사실을 지지하지도 않는 회사가 더 많다는 것은 부끄러운 일이다.

3.2.5 무리 없이 정직하게 반대 의견을 내는 방법

반대 의견은 의사 결정을 잘 하기 위해 꼭 필요한 것이지만, 조심스럽게 다뤄야 한다. 자기 생각을 적절히 표현했고 결정이 내려지기 전에 사람들이 그 생각에 귀를 기울였다고 스스로 느낀다면 좋겠다. 그 경우 더 이상 할 말은 없는 것이고, 이제는 결정된 일에 대해 반대했더라도 그것을 후원할 것인지 결정하면 된다. 반대했더라도 이제 그 결정을 따를 수 있다면 그렇게 하겠다고 말하라. 이것은 내가 생각이 있으며 무조건 찬성하는 사람이 아니지만, 결정된 것은 존중하며 팀의 일원으로 일한다는 자신의 가치를 보여 준다.

어떤 때는 결정 사안에 대해 반대했음에도, 의사결정권자들이 그 의견을 충분히 고려하지도 않고 결정을 내리기도 할 것이다. 이 때에는 문제 제기를 할 것인지 회사나 부족(tribe)의 차원에서 잘 따져 봐야 한다. 그 결정에 사소한 실수가 있는 것 같다면, 재고할 가치는 없을 것이다. 만약 그 실수가 크다면, 당연히 논쟁을 벌여야 한다.

보통은, 이것은 별 문제가 되지 않지만, 스트레스가 많은 상황이나 특별한 성격의 사람들에게는 이것이 개인적인 문제를 일으키기도 한다. 예를 들어, 일 잘 하는 어떤 프로그래머는 결정이 잘못되었다고 믿을 만한 충분한 이유가 있어도 그것에 도전할 자신감이 없다. 더 나쁜 상황이라면, 의사결정권자도 자신감이 없어서 그것을 자기 권위에 대한 개인적인 도전으로 받아들이기도 한다. 이런 경우에 사람들은 비열하게 머리를 써서 반응한다는 것을 꼭 기억할 필요가 있다. 논쟁은 남들이 없을 때 벌여야 하고, 새로운 사실을 알게 됨으로써 전에 내린 결정의 근거가 어떻게 달라지는지 잘 보여주도록 노력해야 한다.

그 결정이 번복되든 그렇지 않든, 그 대안은 충분히 검토되지 않았을 것이므로 나중에라도 '내 그럴 줄 알았어!' 하고 우쭐대지 않도록 하라.

3.3 판단 능력

3.3.1 개발 시간에 맞춰 품질을 조절하는 방법

소프트웨어 개발은 항상 프로젝트의 목적과 프로젝트의 마무리 사이에서 타협하는 일이다. 프로젝트 결과의 배치를 신속하게 하기 위해 공학적 혹은 사업적 감수성을 거스르면서까지 품질을 조절하라는 요구를 받을 수 있다. 예를 들어, 소프트웨어 공학적으로 형편없고 수많은 유지 보수 문제를 일으킬 것이 뻔한 일을 하도록 요구받을 수 있다.

이런 일이 일어난다면 우선 책임 있게 할 일은, 그 사실을 팀에 알리고 품질 저하에 따른 비용을 분명히 설명하는 것이다. 어쨌든, 상사보다는 그 사실에 대해 더 잘 이해하고 있어야 한다. 무엇을 잃을 것이고 무엇을 얻을 것인지, 또 이번에 잃은 것을 다음 단계에 만회하기 위해서는 어떤 비용을 감수해야 하는지 분명히 하라. 이 때, 잘 짜여진 프로젝트 계획서의 선명함이 도움이 될 것이다. 품질을 조절하는 것이 품질 보증의 노력에 영향을 준다면 (상사와 품질보증팀 사람들 모두에게) 그 사실도 지적하라. 품질 조절 때문에 품질 확인 과정을 거치면서 많은 버그가 발견될 것 같다면 그것도 지적하라.

그래도 요구가 계속된다면, 조잡해지는 것이 특정 부분에만 머물게 하여 다음 단계에 재작성이나 개선 계획을 세울 수 있도록 해야 한다. 이 사실을 팀에 알려서 그 계획을 세울 수 있게 하라.

슬래쉬닷(Slashdot)의 닌자프로그래머(NinjaProgrammer)는 이런 보석 같은 글을 보내 왔다.

설계가 좋으면 코드 구현이 나빠도 회복 가능성이 있다는 사실을 기억하라. 코드 전체에 인터페이스와 추상화가 잘 되어 있으면, 언젠가 있을 수 있는 코드 재작성의 고통도 훨씬 덜할 것이다. 코드를 명쾌하게 작성하기도 힘들고 고치기도 힘들다면, 이런 문제를 야기하는 핵심 설계에 무슨 문제가 없는지 생각해 보라.

3.3.2 소프트웨어 시스템의 의존성을 관리하는 방법

최신의 소프트웨어 시스템은 직접 통제할 수 없는 수많은 구성요소(component)들에 의존하는 경향이 있다. 이것은 상승효과(synergy)와 재사용을 통해 생산성을 높인다. 하지만, 각 요소들은 다음의 문제들을 수반한다.
  • 그 구성요소에 있는 버그는 어떻게 고칠 것인가?
  • 그 구성요소 때문에 특정 하드웨어나 소프트웨어 시스템만 사용해야 하는가?
  • 그 구성요소가 기능을 완전히 상실한다면 어떻게 할 것인가?

그 구성요소를 어떻게든 캡슐화 하여 주변과 격리시키고 언제든 다른 것으로 교체할 수 있게 하는 것이 항상 최선이다. 그 요소가 전혀 동작하지 않는 것으로 판명된다면 다른 것으로 대체할 수도 있지만, 직접 작성해야 할 수도 있다. 캡슐화가 이식성(portability)과 같은 말은 아니지만, 쉽게 이식될 수 있게 하기 때문에 거의 그와 같다고 할 수 있다.

구성요소들의 소스 코드를 갖고 있다면 위험 부담이 네 배 정도는 줄어든다. 소스 코드가 있다면 그것을 평가하기도 쉽고, 디버그 하기도 쉽고, 임시방편을 찾기도 쉽고, 수정판을 만들기도 쉽다. 수정판을 만든다면, 그것을 그 구성요소의 소유자에게도 보내 줘서 공식 배포판에 반영하게 해야 한다. 그렇지 않으면 비공식판을 유지하는 불편을 감수해야 할 것이다.

3.3.3 소프트웨어의 완성도를 판단하는 방법

다른 사람들이 작성한 소프트웨어를 사용하는 것은 견고한 시스템을 신속하게 완성하는 가장 효과적인 방법 중 하나이다. 그런 일을 망설일 필요는 없지만, 그에 연관된 위험 요소들을 검사해야 한다. 가장 큰 위험 부담 중 하나는 사용할 제품에 포함되어 사용되는 동안, 완성도를 갖추기 전의 소프트웨어에서 흔히 볼 수 있는 것처럼, 버그가 나타나거나 거의 작동 불능인 상태가 되는 것이다. 어떤 소프트웨어 시스템을 통합할 것인지 고려하기 전에, 그것이 사내에서 만들었든 타사에서 만들었든, 그것이 정말로 사용할 수 있을 만큼 충분한 완성도를 갖췄는지 고려하는 것은 매우 중요하다. 스스로 물어봐야 하는 열 가지 질문이 있다.
  1. 거품(vapor)은 아닌가? (약속만 있는 것은 완성도가 아주 떨어지는 것이다.)
  2. 그 소프트웨어에 대한 평판(lore)이 어떤지 알 수 있는가?
  3. 내가 첫 사용자인가?
  4. 개정판이 계속 나올 만한 충분한 동기(incentive)가 있는가?
  5. 유지 보수 노력이 계속되고 있는가?
  6. 현재 유지 보수 담당자가 없어도 사장되지 않을 것인가?
  7. 절반밖에 안 좋더라도 익숙한 다른 대안(seasoned alternatives)이 있는가?
  8. 부족(tribe)이나 회사에서 그것에 대해 알고 있는가?
  9. 부족이나 회사에서 추천할 만한가?
  10. 문제가 있어도 그것을 해결할 만한 사람을 채용할 수 있는가?

이 항목들을 조금만 생각해 봐도 잘 만들어진 자유 소프트웨어나 공개 소프트웨어가 기업의 위험 부담을 줄이는 데에 얼마나 큰 가치가 있는지 알 수 있을 것이다.

3.3.4 구입과 개발 사이에서 결정하는 방법

사업을 목적으로 한 회사나 프로젝트에서는 소프트웨어로 무엇인가를 달성하기 위해 노력하면서 빈번히 구입과 개발 사이에서 결정을 해야 한다. 이 단계에 왔다는 것은 두 가지 측면에서 불행한 일이다. 즉, 구입하지 않아도 되는 공개 소프트웨어, 자유 소프트웨어를 제쳐놓았기 때문일 것이고, 더 중요한 것은, 통합에 드는 비용도 고려해야 하기 때문에, 구입해서 통합하는 것과 직접 개발해서 통합하는 것 사이에서 결정하는 것을 의미하게 될 것이기 때문이다. 이것은 영업과 경영과 공학적 이해를 전체적으로 결합할 필요가 있다.
  • 자신의 필요와 그 소프트웨어가 설계된 목적이 얼마나 잘 맞는가?
  • 구입한 것 중 얼마만큼이 필요할 것인가?
  • 통합의 가치를 검토하는 데 드는 비용은 얼마나 되는가?
  • 통합하는 데 드는 비용은 얼마나 되는가?
  • 구입하게 되면 장기적 유지 보수 비용은 증가할 것인가, 감소할 것인가?
  • 그것을 구입함으로써 사업상 원치 않는 처지에 있게 되지는 않을 것인가?

다른 사업 전체의 기반이 될 만큼 큰 과제를 직접 개발하는 일은 다시 한 번 생각해 봐야 한다. 그런 생각은 대개 팀에 공헌을 많이 하게 될 똑똑하고 낙관적인 사람들이 제안하기 마련이다. 그들의 생각을 꼭 채택하고 싶다면, 사업 계획을 수정할 각오도 해야 할 것이다. 그러나 뚜렷한 생각 없이 원래 프로젝트보다 더 큰 솔루션(solution) 개발에 투자하는 일은 하지 말라.

이 질문들을 고려한 후에 개발에 대한 것과 구입에 대한 것, 이렇게 두 가지 프로젝트 시안을 준비해야 할 것이다. 통합 비용도 반드시 고려해야 할 것이다. 두 솔루션 모두에 대해 장기적 유지 보수 비용도 고려해야 한다. 통합 비용을 추정하기 위해서는 그 소프트웨어를 구입하기 전에 철저하게 평가해야 할 것이다. 그것을 평가할 수 없다면, 그것을 구입함으로써 생기는 예상 밖의 위험 부담까지 가정하여 그 제품을 구입하는 것에 대해 결정해야 한다. 고려할 구입 결정 대상이 여럿이라면, 각각을 평가하기 위해 상당한 노력을 들여야 할 것이다.

3.3.5 전문가로 성장하는 방법

자신의 권위보다 책임을 더 중하게 생각하라. 바라는 역할에 최선을 다하라. 자신을 개인적으로 도와준 사람들은 물론 조직 전체의 성공에 기여한 사람들에게 감사하라.

팀장이 되고 싶다면, 팀의 합의를 이루기 위해 애쓰라. 관리책임자가 되고 싶다면, 작업 일정에 책임을 지라. 팀장이나 관리책임자 대신 이 일을 맡는다면, 그들이 자유롭게 더 큰 일에 전념할 수 있을 것이므로, 그 일을 편하게 해 낼 수 있을 것이다. 그 일이 시험 삼아 해 보기에 너무 크다면, 한 번에 조금씩 하도록 하라.

자기 자신을 평가하라. 더 나은 프로그래머가 되고 싶다면, 존경하는 사람에게 어떻게 하면 그들과 같이 될 수 있는지 물어보라. 상사에게 물어볼 수도 있다. 그가 아는 것은 적어도 나의 경력에는 큰 영향을 미칠 것이다.

자기 일에 활용할 수 있는 새로운 기능을 배울 방법을 계획하라. 이것은, 새로운 소프트웨어 시스템에 대해 배우는 것과 같이 사소한 기술적 기능일 수도 있고, 글을 잘 쓰는 것과 같이 어려운 사회적 기능일 수도 있다.

3.3.6 면접 대상자를 평가하는 방법

사원이 될 사람들을 평가하는 일은, 그 가치에 비해 큰 노력을 들이지 않고 있다. 잘못된 채용은, 잘못된 결혼과 마찬가지로, 끔찍한 일이다. 모든 사람들은 자기 에너지의 상당 부분을 인재 발굴에 바쳐야 하지만, 실제로 그런 경우는 드물다.

다양한 면접 유형이 있다. 어떤 것은 고문과 같아서, 지원자가 심한 스트레스를 받게 되어 있다. 이것은 스트레스를 받는 상황에서 인격적인 결점과 약점이 드러날 수 있게 하는 매우 중요한 목적이 있다. 지원자들은 자기 자신에 대해 정직한 만큼 면접관에게 정직할 것이다. 그런데 인간의 자기기만(self-deception) 능력은 대단하다.

최소한 두 시간 동안은 지원자에게 기술적 기능에 대해 묻는 구두시험을 실시해야 한다. 여러 번 하다 보면, 그들이 아는 것이 무엇인지 즉시 파악하고, 경계를 명확히 하기 위해 그들이 모르는 것에 대해 즉시 반응할 수 있게 될 것이다. 면접 대상자들은 이것을 순순히 받아들일 것이다. 나는 면접 대상자들에게서 회사를 선택하는 동기 중 하나가 면접의 질적 수준이라는 말을 몇 번 들은 적이 있다. 좋은 사람들은, 전에 일하던 곳이 어디인지, 어느 학교를 나왔는지, 그 밖의 다른 사소한 특성들보다는 자기 실력 때문에 채용되기를 원한다.

면접을 하면서 그들의 학습 능력에 대해서도 평가해야 한다. 이것은 그들이 현재 알고 있는 것이 무엇인가보다 더욱 더 중요하다. 까다로운 사람이라는 낌새도 알아차려야 한다. 면접 후에 기록해 둔 것들을 비교하면서 이런 것을 알아차릴 수 있을 것이다. 하지만 한참 면접이 진행되는 동안에 그것을 알아차리기는 어렵다. 다른 사람과 의사소통하고 같이 일하는 것을 얼마나 잘 하는가는 최신 프로그래밍 언어에 능통한 것보다 중요하다.

한 독자는 면접 대상자들에게 집에서 풀어오는 시험을 실시하여 결과가 좋았다고 한다. 이 방법은 면접 때 말은 잘 하지만 정작 코딩은 못 하는 사람들을 추려낼 수 있다는 장점이 있다. (사실 그런 사람들이 많다.) 개인적으로 이 기법을 써 보지는 않았지만, 괜찮아 보인다.

끝으로, 면접은 판매의 과정이기도 하다. 지원자들에게 회사나 프로젝트를 잘 팔아야 한다. 하지만, 프로그래머에게 이야기하는 것이므로, 진실을 윤색하려고 하지는 말라. 나쁜 점에서 시작하여 좋은 점에 대해 강한 인상을 주면서 마무리 하라.

3.3.7 화려한 전산 과학을 적용할 때를 아는 방법

많은 프로그래머들이 알기는 하지만 거의 사용하지 않는 알고리듬, 자료 구조, 수학, 그 밖의 거창한 내용에 대한 지식들이 있다. 실제로 이런 훌륭한 지식들은 너무 복잡하여 일반적으로는 필요가 없다. 예를 들어, 대부분의 시간을 비효율적인 데이터베이스 질의를 만들고 있으면서 알고리듬을 개선한다는 것은 아무 의미가 없다. 프로그래밍을 하면서 시스템들이 서로 통신하게 한다거나 멋있는 사용자 환경(user interface)을 만들기 위해 매우 단순한 자료 구조를 사용해야 한다면 불행이 시작되는 것이다.

고급 기술을 사용하는 것이 적절한 때는 언제인가? 흔하지 않은 다른 알고리듬을 찾기 위해 책을 펼쳐야 하는 때는 언제인가? 그런 것을 사용하는 것이 유용할 경우가 있지만, 그 전에 주의해서 평가해야 한다.

사용하게 될지 모르는 전산 과학 기법에 대해 고려해야 하는 매우 중요한 세 가지 측면이 있다.
  • 캡슐화가 잘 되어 있어서, 다른 시스템에 미칠 위험 부담이 적고, 복잡도나 유지 보수 비용의 증가가 적은가?
  • 그 이득이 대단한가? (예를 들어, 기존의 것과 유사한 시스템이라면 두 배, 새로운 시스템이라면 열 배 정도의 이득)
  • 그것을 효과적으로 검사하고 평가할 수 있을 것인가?

다소 화려해도 주변과 격리가 잘 되어 있는 알고리듬은 전체 시스템에 대해 두 배 정도로 하드웨어 비용을 줄이거나 그 만큼 성능을 향상시킬 수 있을 것이므로 그것을 고려하지 않는 것은 어리석은 일이 될 것이다. 이런 해결 방법을 주장하기 위한 한 가지 열쇠는, 그 기술에 대한 연구는 이미 잘 되어 있을 것이고 남은 문제는 통합의 위험 부담일 것이므로, 그 위험 부담이 실제로 매우 적다는 것을 보여 주는 것이다. 이렇게 될 때 프로그래머의 경험과 판단 능력이 그 화려한 기술과 어울려 진정한 상승효과를 낼 수 있을 것이며 통합도 쉽게 이뤄질 것이다.

3.3.8 비기술자들과 이야기하는 방법

대중문화에서 기술자, 특히 프로그래머는 일반적으로 보통 사람들과는 다른 사람이라고 인식된다. 이것은 보통 사람들이 우리와 다르다는 뜻이다. 비기술자들과 대화할 때 이 사실을 염두에 두고 있는 것이 중요하다. 항상 듣는 사람을 이해해야 한다.

비기술자들은 똑똑하더라도 우리처럼 기술적인 것들을 만드는 일에 기초가 있지는 않다. 우리는 무엇인가를 만든다. 그들은 무엇인가를 팔거나 다루거나 세거나 관리하지만, 만드는 일에는 전문가가 아니다. 그들은 기술자들처럼 (물론 예외는 있지만) 팀으로 같이 일하는 것에도 익숙하지 않다. 주: 많은 독자들이 이 절의 내용이 오만하거나 자기 경험과는 거리가 멀다고 느낄 수 있다. 나는 비기술자도 매우 존중한다. 겸손한 척하려고 하는 말이 아니다. 나의 이런 신념들로 기분이 상했다면 용서를 구한다. 하지만 솔직하게 말해서 반대 사례를 경험하게 될 때까지는 그 신념을 거둘 수 없을 것 같다. 내가 이례적으로 운이 좋아서 그 동안 좋은 프로그래머들과 같이 일해 왔을 수도 있고, 다른 사람들이 프로그래머는 대화하기 부담스럽다는 고정관념(stereotype)을 일반적인 표준으로 생각하는 것일 수도 있다. 그들의 사회적 기능은 일반적으로, 팀이 아닌 환경에서 일하는 기술자들과 같거나 더 낫지만, 그들이 하는 일은 우리처럼 깊고 정확하게 의사소통을 해야 하거나 세부 과제를 조심스럽게 나눠야 하는 등의 일이 항상 요구되지는 않는다.

비기술자들은 간절히 다른 사람을 만족시키고 싶어 할 수도 있고, 기술자들에게 위협을 느낄 수도 있다. 우리와 똑같이, 그들도 기술자들을 만족시키기 위해서, 혹은 기술자들에게 다소 겁을 먹어서, 별 뜻 없이 '예'라고 말해 놓고는 나중에는 그 말에 책임지지 않을 수 있다.

비프로그래머들이 기술적인 것들을 이해할 수는 있지만 우리에게도 어려운 그것, 즉 기술적 판단 능력은 없다. 그들은 기술이 어떻게 적용되는지는 이해하지만, 왜 어떤 접근 방식은 석 달이나 걸리고, 다른 방식은 사흘이면 되는지 이해하지 못한다. (어쨌든, 프로그래머들이 이런 종류의 추정에 너무 냉정하다는 것도 맞는 말이다.) 이것은 그들과 함께 상승효과를 낼 수 있는 기회가 있다는 뜻도 된다.

팀에서 이야기할 때에는, 별 생각 없이, 일종의 줄임말을 사용할 것이다. 일반 기술이나 특히 함께 작업하는 제품에 대해 많은 경험을 공유하고 있기 때문에 그것이 효과적이다. 그런 경험의 공유가 없는 사람들에게 이야기할 때, 특별히 자기 팀원들도 같이 있다면, 줄임말을 사용하지 않는 데에 노력을 좀 들여야 한다. 이런 어휘는 우리와 그것을 공유하지 않는 사람들 사이에 벽을 만들고, 더 나쁘게는, 그들의 시간을 낭비하게 한다.

팀원들과 있을 때는 기본 가정이나 목표를 수시로 다시 말할 필요는 없으며, 대부분의 대화가 세부적인 것들에 초점이 맞춰진다. 외부인들과 함께 있을 때는 다른 방식으로 해야 한다. 그들은 우리가 당연하게 여기는 것을 이해하지 못할 수 있다. 어떤 것을 당연하게 여기고 다시 설명하지 않기 때문에, 실제로는 커다란 오해가 있는데도 서로를 잘 이해했다고 생각하면서 외부인들과 대화를 마칠 수도 있다. 자기 생각을 잘못 전달할 수 있다는 것을 항상 가정하고 실제로 그런 일이 없는지 잘 살펴봐야 한다. 그들이 잘 이해했는지 알아보기 위해, 요약을 하게 하거나 다른 말로 표현하게 해 보라. 그들을 자주 만날 기회가 있다면, 내가 효과적으로 대화하고 있는지, 어떻게 하면 더 잘 할 수 있을지 잠깐 물어보는 것도 좋다. 의사소통에 문제가 있다면, 그들에게 실망하기 전에 자신의 습관을 고칠 방법을 찾으라.

나는 비기술자들과 같이 일하는 것을 좋아한다. 가르치고 배울 기회가 많기 때문이다. 명확한 용어로 대화하면서, 예를 들어 가며 안내할 수도 있을 것이다. 기술자들은 무질서에서 질서를, 혼동됨에서 명확함을 찾아내도록 훈련받으며, 비기술자들은 우리의 이런 점을 좋아한다. 우리는 기술적 판단 능력이 있고 사업상의 문제들도 대개 이해할 수 있기 때문에, 종종 문제에 대한 간단명료한 해결책을 찾아내기도 한다.

비기술자들은 좋은 뜻으로, 그리고 잘 해 보려는 마음으로 우리가 일을 더 쉽게 해 낼 수 있을 것이라고 생각하는 해결책들을 제안하기도 한다. 사실은 훨씬 더 좋은 종합적 해결책이 존재하는데, 그것은 외부인들의 관점과 우리의 기술적 판단력이 함께 상승효과를 낼 때에만 보인다. 나는 개인적으로 익스트림 프로그래밍(Extreme Programming)을 좋아한다. 그것이 이런 비효율성을 중점적으로 다루고 있으며, 아이디어와 그에 대한 비용 추정을 신속하게 짝지음으로써, 비용과 이득이 최상으로 결합되는 아이디어를 쉽게 찾을 수 있게 해 주기 때문이다.

4 상급자

4.1 기술적 판단 능력

4.1.1 어려운 것과 불가능한 것을 구분하는 방법

어려운 일은 해 내고, 불가능한 일은 골라내는 것이 우리가 할 일이다. 대부분의 현직 프로그래머들의 관점에서 보면, 단순한 시스템에서 나올 수 없거나 비용을 추정할 수 없는 일은 불가능한 것이다. 이 정의에 따르면 연구라고 불리는 것은 모두 불가능한 일이다. 일거리들의 많은 부분이 어렵기는 하지만, 반드시 불가능한 것은 아니다.

이 구분은 전혀 우스운 것이 아니다. 과학적 관점에서든 소프트웨어 공학적 관점에서든, 실제로 불가능한 일을 하라는 요구를 받는 경우가 많을 것이기 때문이다. 어렵기는 해도 사업주가 원하는 것을 최대한 끌어낼 수 있는 합리적인 해결책을 찾도록 돕는 것이 우리가 할 일이다. 자신 있게 일정을 잡을 수 있고 위험 부담을 잘 이해하고 있다면, 그 해결책은 어려운 것일 뿐이다.

예를 들어, '각 사람에게 가장 매력적인 머리 모양과 색깔을 계산할 수 있는 시스템을 개발하라'와 같은 막연한 요구 사항을 만족시키는 것은 불가능한 일이다. 요구 사항이 좀 더 뚜렷해질 수 있다면, 그 일이 어려울 뿐인 일로 바뀌기도 한다. 예를 들어 다음과 같은 식이다. '어떤 사람에게 매력적인 머리 모양과 색깔을 계산할 수 있는 시스템을 개발하되, 그들이 그것을 미리 보고 수정할 수 있게 하여, 처음 제안한 스타일에 대한 고객 만족을 극대화함으로써 많은 수입을 얻을 수 있게 하라.' 성공에 대한 뚜렷한 정의가 없다면 성공할 수 없을 것이다.

4.1.2 내장 언어를 활용하는 방법

시스템에 프로그래밍 언어를 내장하는(embedding) 것은 프로그래머에게는 에로틱하다고 할 만한 황홀함을 느끼게 한다. 이것은 해 볼 수 있는 가장 창의적인 활동들 중 하나이다. 이것은 시스템을 굉장히 강력하게 만들어 준다. 이것을 통해 자신의 창의적이고 프로메테우스적인 능력을 최대한 발휘할 수 있다. 이것은 시스템을 친구로 만들어 준다.

세계적으로 가장 우수한 텍스트 편집기들은 모두 내장 언어를 갖추고 있다. 사용자가 그 언어에 완전히 통달하는 경지에까지 이를 수도 있다. 물론, 그것이 텍스트 편집기 안에 들어 있으므로, 써 보고 싶은 사람들은 쓸 수 있고 그렇지 않은 사람들은 그럴 필요 없도록, 그 언어의 사용을 선택 사항으로 둘 수도 있다.

나를 비롯하여 다른 많은 프로그래머들이 특수한 목적의 내장 언어를 만들고 싶다는 유혹에 빠지곤 한다. 나는 두 번 그런 적이 있다. 내장 언어로 특별히 설계된 언어들이 이미 많이 나와 있다. 새로운 것을 또 만들기 전에 한 번 더 생각해 볼 필요가 있다.

언어를 내장하기 전에 스스로 물어봐야 하는 진짜 질문은 이것이다. 이것이 사용자의 문화와 잘 맞을 것인가, 그렇지 않을 것인가? 사용자가 모두 비프로그래머라면 그것이 무슨 도움이 될 것인가? 사용자가 모두 프로그래머라면 오히려 API를 선호하지 않을 것인가? 무슨 언어로 할 것인가? 프로그래머들은 사용 범위가 좁은 새 언어는 배우고 싶어 하지 않는다. 하지만 그것이 그들의 문화와 잘 맞물린다면 많은 시간을 들이지 않고서도 배울 수 있을 것이다. 새로운 언어를 만든다는 것은 즐거운 일이다. 하지만 그렇다고 해서 사용자들의 필요에 대해 눈이 가려져서는 안 된다. 정말로 근본적인 필요와 아이디어가 있는 것이 아니라면, 사용자들이 이미 친숙한 기존의 언어를 사용해서 부담을 줄여 주는 것이 어떤가?

4.1.3 언어의 선택

자신의 일을 사랑하는 고독한 프로그래머(즉, 해커)는 과제에 가장 잘 맞는 언어를 선택할 수 있다. 대부분의 현직 프로그래머들은 자기가 사용할 언어를 마음대로 고를 수 있는 경우가 드물다. 일반적으로, 이 문제는 잘난 체하는(pointy-haired) 상사들이 마음대로 결정한다. 이들은 기술적으로 결정하기보다는 정략적으로 결정하고, 아직 일반화되지 않은 어떤 도구가 가장 좋다는 것을 (대개 실무 경험에 의해) 알면서도 재래식이 아닌 도구를 사용하자고 나설 만한 용기는 없다. 어떤 경우에는 팀 전체, 더 넓게는 공동체 전체의 통일이 매우 실제적인 이득이 있기 때문에 개인적인 입장에서 선택하는 것을 배제하기도 한다. 관리책임자들은 정해진 언어에 대한 경험이 있는 프로그래머들을 채용해야 하는 필요에 따라 움직이기도 한다. 그들이 프로젝트나 회사에 가장 큰 이익이 된다고 생각하는 것을 위해 일한다는 것은 분명하며, 그것에 대해 존중받을 만하다. 하지만 나는 개인적으로 이것이 흔히 마주치게 되는 가장 낭비적이고 잘못된 일이라고 생각한다.

물론, 모든 일이 1차원적인 경우는 없다. 한 가지 중심 언어가 필수로 정해지고 그것을 내가 어떻게 할 수 없다 해도, 도구나 다른 프로그램을 다른 언어로 작성할 수 있거나 그렇게 해야 하는 경우가 종종 있다. 언어를 내장해야 한다면 (이것은 항상 생각해야 한다!) 언어를 선택할 때 사용자들의 문화를 많이 고려해야 할 것이다. 회사나 프로젝트에 기여하기 위해 그 일에 가장 적합한 언어를 사용하는 것의 장점을 잘 활용해야 하며, 이것을 통해 일이 더욱 흥미로워질 것이다.

프로그래밍 언어는, 그것을 배우는 것이 자연 언어를 배우는 것만큼 어려운 일이 전혀 아니라는 점에서, 표기법들(notations)이라고 부르는 것이 실제에 가깝다. 초보자들이나 외부인들에게는 "새로운 언어 배우기"가 멈칫하게 될 과제로 보인다. 하지만 세 가지 정도 언어를 체험해 보면, 그 일은 주어진 라이브러리들에 익숙해지는 문제일 뿐이다. 구성요소들이 서너 가지 언어로 되어 있는 큰 시스템이 있을 때 그것을 지저분하게 뒤범벅이 되어 있다고 생각할 수 있지만, 나는 그런 시스템이 한 가지 언어만으로 되어 있는 시스템보다 여러 면에서 더 튼튼한 경우가 많다고 하겠다.
  • 서로 다른 표기법으로 작성된 구성요소들은 필연적으로 결합도(coupling)가 낮아진다. (깔끔한 인터페이스는 없겠지만)
  • 각 요소들을 개별적으로 재작성함으로써 새로운 언어/플랫폼으로 발전시키기 쉽다.
  • 실제로는 어떤 모듈들이 최신의 것으로 갱신되었기 때문일 수도 있다.

심리적인 효과일 뿐인 것도 있지만, 심리적인 것도 중요하다. 결국 언어에 대한 독재는 그것이 주는 유익보다 거기에 드는 비용이 더 크다.

4.2 현명하게 타협하기

4.2.1 작업 일정의 압박과 싸우는 방법

출시 시간(time-to-market)의 압박은 좋은 제품을 신속하게 내놓기 위한 압박이다. 이것은 재정적 현실을 반영하는 것이기 때문에 나쁠 것도 없고, 어떤 점에서는 건전한 것이다. 작업 일정의 압박은 내놓을 수 있는 시간보다 더 빨리 내놓기 위한 압박이며, 이것은 낭비적이고 건전하지도 않지만, 너무도 흔하다.

작업 일정의 압박은 몇 가지 이유로 존재한다. 프로그래머들에게 과제를 맡기는 사람들은 우리가 얼마나 강한 직업윤리를 갖고 있으며 프로그래머가 된다는 것이 얼마나 재미있는 일인지 충분히 인식하지 못한다. 아마도 그들은 자신의 행동 방식을 우리에게 그대로 비춰 보기 때문에, 더 빨리 하라고 요구하면 더 열심히 일하게 될 것이라고 믿는다. 이것은 어쩌면 실제로 사실일 수도 있지만, 그 효과는 매우 작으며 손해는 매우 크다. 게다가 그들은 소프트웨어를 만들기 위해 실제로 무엇이 필요한지 볼 수 있는 눈이 없다. 볼 수도 없고 스스로 만들 수도 없기 때문에, 그들이 할 수 있는 단 한 가지는 출시 시간의 압박을 보면서 프로그래머들에게 그것에 대해 떠들어대는 일이다.

작업 일정의 압박과 싸우는 열쇠는 그것을 출시 시간의 압박으로 바꿔 놓는 것이다. 이렇게 하는 방법은 가용 인력과 제품 사이의 관계를 잘 볼 수 있게 하는 것이다. 개입된 모든 인력에 대해 정직하고 상세하고 무엇보다도 이해할 만한 추정치를 내놓는 것이 이것을 위한 가장 좋은 방법이다. 이것은 직무의 조정 가능성에 대한 관리상의 의사결정을 잘 할 수 있게 해 준다는 추가적인 장점이 있다.

이런 추정을 통해 명백해지는 중요한 통찰은, 인력이 비압축성 유체(incompressible fluid)와 같다는 것이다. 그릇의 부피보다 더 많이 물을 눌러넣을 수 없듯이, 일정 시간 안에 더 많은 것을 우겨넣을 수 없다. 어떤 점에서 프로그래머는 '못 합니다.'라고 하기보다는, '원하는 그 일을 위해 무엇을 포기하겠습니까?'라고 해야 할 것이다. 추정을 명확하게 함으로써 프로그래머가 더욱 존중받게 되는 효과가 있을 것이다. 다른 직종의 전문가들은 바로 이렇게 행동한다. 이로써 프로그래머들의 고된 일이 눈에 보이게 될 것이다. 비현실적인 작업 일정을 잡았다는 사실도 고통스럽겠지만 모든 사람에게 분명히 드러날 것이다. 프로그래머들은 함부로 현혹할 수 있는 사람들이 아니다. 그들에게 비현실적인 것을 요구하는 일은 예의 없고 비도덕적인 일이다. 익스트림 프로그래밍(Extreme Programming)은 이것을 상세히 설명하고 있으며 그 과정을 확립해 놓고 있다. 나는 모든 독자들이 이 기법을 활용할 수 있을 만큼 운이 좋기를 바란다.

4.2.2 사용자를 이해하는 방법

우리에게는 사용자를 이해하고, 또한 상사가 그 사용자를 이해할 수 있게 도와 줄 의무가 있다. 사용자는 우리처럼 제품 생산에 깊이 개입되어 있지 않기 때문에 다음과 같이 조금 특이하게 행동한다.
  • 사용자는 일반적으로 짧게 말하고 끝낸다.
  • 사용자는 자기 일이 따로 있다. 그들은 대개 제품이 크게도 아니고 약간 개선되었으면 좋겠다고 생각한다.
  • 사용자는 그 제품 사용자들 전체를 볼 수 있는 눈이 없다.

우리는 그들이 원한다고 말한 것이 아니라, 정말로 그들이 원하는 것을 제공할 의무가 있다. 그래서 일에 착수하기 전에 이쪽에서 먼저 제안을 하고, 그것이 정말로 그들이 원하는 것이라는 동의를 얻는 것이 더 나을 수 있다. 하지만 그들은 이렇게 하는 것이 바람직하다는 것을 모를 수도 있다. 그것을 위한 자신의 아이디어에 대한 자신감의 정도는 경우에 따라 달라져야 한다. 고객이 정말로 무엇을 원하는지 아는 것에 대해, 자만심과 거짓 겸손, 둘 다 경계해야 한다. 프로그래머들은 설계하고 만들어내도록 훈련된다. 시장 연구자들은 사람들이 무엇을 원하는지 알아내도록 훈련된다. 이 두 종류의 사람들, 아니 한 사람 안에 있는 두 가지 사고방식은 함께 조화를 이룰 때 정확하게 통찰할 수 있는 최상의 조건이 된다.

사용자들과 시간을 많이 보낼수록 무엇이 실제로 성공적일지 더 잘 이해하게 될 수 있을 것이다. 가능한 한 많이 자신의 생각을 사용자들의 생각과 비교하여 검사해 봐야 한다. 할 수 있다면 그들과 함께 먹고 마시기도 해 봐야 한다.

가이 카와사키(Guy Kawasaki)는 사용자들의 말을 듣는 것에 더하여 그들이 무엇을 하는지 관찰하는 것의 중요성을 강조한 바 있다. <Rules>

내가 알기로, 의뢰인들이 진정으로 원하는 것이 무엇인지 그들 자신의 마음에 분명해지게 하는 일에 계약직 프로그래머나 컨설턴트들이 엄청난 어려움을 겪는 경우가 종종 있다. 컨설턴트가 될 생각이 있는 사람은 의뢰인을 선택할 때 그들의 수표책뿐만 아니라 그들의 머리 속이 얼마나 명료한지도 확인하라고 권하고 싶다.

4.2.3 진급하는 방법

어떤 역할로 진급하고 싶다면, 그 역할을 먼저 실행하라.

어떤 직위로 진급하고 싶다면, 그 직위에 기대되는 것이 무엇인지 파악하여 그것을 행하라.

임금 인상을 원한다면, 정확한 정보로 무장하고 협상하라.

진급을 할 때가 지났다고 느껴지면, 상사에게 그것에 대해 이야기하라. 진급을 하기 위해 무엇을 해야 하는지 숨기지 말고 그들에게 질문하라. 진부한 이야기로 들리겠지만, 스스로 무엇이 필요하다고 인식하는 것과 상사가 인식하는 것이 상당히 다른 경우가 종종 있다. 또한 이것은 어떤 식으로든 상사에게 그 일을 확실히 못 박아 두는 것도 된다.

대부분의 프로그래머들이 자신의 상대적 능력에 대해 어떤 면에서는 과장되게 생각하는 것 같다. 하지만, 우리가 모두 상위 10%가 될 수는 없는 노릇이다! 그러나, 심각하게 진가를 인정받지 못하는 사람들도 많이 봐 왔다. 모든 사람의 평가가 항상 정확하게 실체와 일치할 것이라고 기대할 수는 없지만, 한 가지 단서가 있다면 사람들은 일반적으로 적당히 공정할 것이라고 생각한다. 자신의 일을 드러내 보여주지 않는다면 제대로 평가받을 수도 없다. 때로는 우연한 실수나 개인적인 버릇 때문에, 충분히 주목받지 못하기도 한다. 집에서 주로 일하거나 팀이나 상사와 지리적으로 떨어져 있는 것 때문에 이것이 특히 어려워지기도 한다.

4.3 팀을 위해 일하기

4.3.1 재능을 개발하는 방법

니체(Nietzsche)는 이렇게 과시하며 말했다. <Stronger>

나를 파괴하지 않는 것은 나를 강하게 하는 것이다.


우리가 가장 많이 책임져야 할 대상은 우리 팀이다. 팀원들을 모두 잘 알아야 한다. 팀에게 도전적으로 요구하더라도, 지나치게 무거운 짐을 지워서는 안 된다. 그들이 긴장을 유지하는 방법에 대해 그들과 이야기해 봐야 할 것이다. 그들이 그것을 기꺼이 받아들인다면(buy in), 더욱 동기가 높아질 것이다. 모든 프로젝트, 또는 하나 건너 하나의 프로젝트마다 그들이 제안한 방법과 그들에게 좋을 것 같다고 생각되는 방법으로 긴장을 유지하게 해 줘야 한다. 그들에게 일을 더 많이 맡기는 것보다는, 새로운 기능을 알려주거나 더 좋게는 팀에서 능력을 발휘할 새로운 역할을 부여하여 긴장을 유지하게 하라.

다른 사람들은 물론 자기 자신도 간혹 일이 안 풀릴 수 있다는 것을 인정해야 하며, 일정대로 일이 진행되지 않을 경우를 대비한 계획을 세워 놓아야 한다. 항상 일이 잘 된다면, 모험은 아무 의미가 없을 것이다. 일이 안 풀리는 경우가 없다는 것은, 위험 부담 없이 편하게만 일을 하고 있다는 뜻이다. 누군가 일이 잘 안 됐다면, 그들이 성공한 것처럼 대우할 필요는 없겠지만, 최대한 부드럽게 대해야 한다.

모든 팀원들이 기꺼이 받아들이고 동기를 높일 수 있도록 노력하라. 팀원 각자에게 그들이 동기가 높지 않을 때 어떻게 해 주면 좋은지 터놓고 물어 보라. 그들을 불만족스러운 채로 내버려둬야 할 경우도 있지만, 각자가 바라는 것이 무엇인지는 알고 있어야 한다.

낮은 의욕이나 불만족 때문에 자기 몫의 짐을 일부러 지지 않는 사람을 무시해 버리거나 되는 대로 내버려 둘 수는 없다. 그들의 동기와 생산성을 높이도록 노력해야 한다. 참을 수 있는 한 계속 노력하라. 인내의 한계를 넘어섰다면 그들을 해고하라. 일부러 자기 능력 이하로 일하는 사람들을 팀에 계속 있게 할 수는 없다. 그렇게 하는 것은 팀에 공정한 일이 아니다.

능력 있는 팀원들에게는 그들이 능력 있다고 생각한다는 사실을 공개적으로 이야기함으로써 그 사실을 확인시켜 주라. 칭찬은 공개적으로, 비판은 사적으로 해야 한다.

능력 있는 팀원들은 자연적으로 능력이 모자라는 팀원들보다 더 어려운 과제를 맡는다. 이것은 아주 자연스러운 일이며, 모두가 다 열심히 일하는 한 아무도 이것 때문에 귀찮아하지 않을 것이다.

좋은 프로그래머 한 사람이 안 좋은 프로그래머 열 사람보다 생산성이 높은데도 그것이 봉급에 반영되지 않는 것은 이상한 일이다. 이것 때문에 미묘한 상황이 생긴다. 능력이 모자란 프로그래머가 길을 비켜 주면 일을 더 빨리 진행할 수 있는 경우가 사실 종종 있다. 그렇게 하면 실제로 단기간에는 더 많은 성과를 낼 수도 있다. 하지만, 부족(tribe) 전체로서는 여러 중요한 이득을 잃게 된다. 능력이 모자라는 팀원의 훈련, 팀 내 지식의 확산, 능력 있는 팀원이 없을 때 대신할 능력 등이 그것이다. 능력 있는 사람들은 이 점에 관해서는 너그러울 필요가 있고 그 문제를 다각도로 고려해야 한다.

능력 있는 팀원에게는 도전할 만하면서 범위가 잘 정의된 과제를 맡겨 보는 것도 좋다.

4.3.2 일할 과제를 선택하는 방법

프로젝트의 어느 부분을 맡아 할 것인지 선택할 때에는 자신의 개인적 필요와 팀 전체의 필요 사이에서 균형을 잡아야 한다. 물론 가장 잘 하는 일을 선택해야겠지만, 일을 더 많이 하는 것보다는 새로운 기능을 발휘할 수 있는 것을 통해 스스로 긴장을 유지할 수 있는 길을 찾아보도록 하라. 지도력과 의사소통 기능은 기술적 기능보다 더 중요하다. 자신의 능력이 뛰어나다면, 더 어렵고 위험부담이 큰 과제를 맡고, 위험부담을 줄이기 위해 프로젝트에서 그 일을 최대한 빨리 처리하라.

4.3.3 팀 동료들이 최대한 능력을 발휘하게 하는 방법

팀 동료들이 최대한 능력을 발휘하게 하기 위해서는, 단체정신을 키우고 팀원 모두가 개별적으로 도전을 느끼고 개별적으로 일에 몰두하도록 격려하라.

단체정신을 키우기 위해서는, 로고가 새겨진 옷이나 파티처럼 다소 진부한 것도 좋지만, 개인적으로 존중하는 것만큼 좋지는 않다. 모두가 모두를 존중하면 아무도 다른 사람을 깎아내리고 싶지 않을 것이다. 단체정신은 사람들이 팀을 위해 희생하고 자신의 이익보다 팀의 이익을 먼저 생각할 때 만들어진다. 팀장으로서 이 점에 대해 솔선해서 한 것 이상으로 다른 사람에게 요구할 수는 없을 것이다.

팀에 대한 지도력의 한 가지 열쇠는 모든 사람이 기꺼이 받아들일 수 있는 합의를 이뤄내는 것이다. 이것은 팀 동료들이 잘못된 일을 하는 것을 인정한다는 말이기도 하다. 즉, 그것이 프로젝트에 너무 큰 피해를 주지만 않는다면, 비록 나는 그렇게 하는 것이 잘못된 것이라는 분명한 확신이 있다 해도, 팀원 중 누가 자기 방식대로 일하는 것을, 합의에 따라 그렇게 하게 두어야 한다. 이런 일이 생길 때는, 찬성하지 말고 공개적으로 반대하되, 합의된 것은 받아들이라. 마음이 상했다거나 어쩔 수 없이 그렇게 한다는 식으로 말하지 말고, 그것에 반대하지만 팀의 합의를 더 중요하게 생각한다고 꾸밈없이 말하라. 이렇게 할 때 그들이 돌이키기도 한다. 그들이 돌이켰다면 그들에게 원래 계획대로 밀고 나가라고 고집하지는 말라.

그 문제에 대해 적절한 모든 관점에서 논의한 후에도 동의하지 않는 사람이 있다면, 이제 결정을 내려야 하며 이것이 나의 결정이라고 꾸밈없이 단언하라. 자신의 결정이 잘못되었는지 판단할 방법이 있거나 나중에 잘못된 것으로 밝혀진다면 할 수 있는 대로 빨리 돌이키고 옳았던 사람을 인정하라.

단체정신을 키우고 팀을 효과적으로 만들기 위해 어떻게 하는 것이 좋을지 팀 전체와 팀원 각자에게 물어보라.

자주 칭찬하되 분별없이 하지는 말라. 특별히 나와 의견이 다른 사람도 칭찬할 만할 때에는 바로 칭찬하라. 공개적으로 칭찬하고 사적으로 비판하라. 한 가지 예외는 있다. 과실을 바로잡아 가치가 증대된 것(growth)을 공개적으로 칭찬할 때에는 원래의 과실이 눈길을 끄는 난처한 상황이 될 수도 있으므로, 가치 증대에 대해서는 사적으로 칭찬하는 것이 좋다.

4.3.4 문제를 나누는 방법

소프트웨어 프로젝트를 맡아서 각 사람이 수행할 과제들로 나누는 것은 재미있는 일이다. 이것은 초반에 해야 한다. 관리책임자들이 그 일을 수행할 각 사람들에 대해 고려하지 않아도 시간을 추정할 수 있다고 생각하는 경우가 있는 것 같다. 이것은 불가능한 일이다. 각자의 생산성은 아주 크게 차이가 나기 때문이다. 어떤 구성요소(component)에 대한 특정 지식이 있는 사람도 계속 변화해 가므로, 수행 능력에서 여러 배의 차이가 날 수도 있다.

지휘자가 연주할 악기의 음색을 고려하고 운동 경기 코치가 각 선수의 능력을 고려하는 것처럼, 경험 많은 팀장이라면 프로젝트를 과제들로 나누는 일을, 그 과제가 할당될 팀원들과 분리해서 생각하지는 않을 것이다. 이것은 수행 능력이 뛰어난 팀이 해체되어서는 안 되는 이유이기도 하다.

여기에도 위험이 다소 따르는데, 사람들이 능력을 쌓아가는 과정에서 따분해져서 약점을 개선하거나 새로운 기능을 배우려고 하지 않는 경우가 그것이다. 하지만, 전문화는 남용되지만 않는다면 생산성 향상에 매우 유용한 도구가 된다.

4.3.5 따분한 과제를 다루는 방법

간혹 회사나 프로젝트의 성공에 결정적이기 때문에 과제가 따분해도 피할 수 없는 경우가 있다. 이런 과제들은 그것을 해야 하는 사람들의 사기를 실제로 떨어뜨릴 수 있다. 이것을 다루는 가장 좋은 기법은 래리 월(Larry Wall)이 말한 '프로그래머의 게으름의 미덕(virtue of Laziness)'을 불러일으키고 북돋아 주는 것이다. 자신이나 동료 대신 컴퓨터가 그 과제를 처리하게 하는 방법이 없는지 찾아보라. 수작업으로 한 주에 할 과제를 해결할 프로그램을 짜는 데 한 주가 걸린다 해도 이것이 더욱 교육적이고 필요하면 반복해서 쓸 수도 있으므로 더 큰 이득이 있는 것이다.

모든 방법이 수포로 돌아간다면, 따분한 일을 해야 하는 사람들에게 양해를 구해야겠지만 어떤 경우에도 그들이 혼자 하게 내버려 두지는 말라. 최소한 두 사람으로 팀을 짜서 그 일을 맡기고 그 일을 완수하기 위해 성실히 협력할 수 있도록 격려하라.

4.3.6 프로젝트를 위한 지원을 얻는 방법

프로젝트를 위한 지원을 얻기 위해서는, 조직 전체가 추구할 실제적인 가치를 잘 드러내는 이상(vision)을 만들고 알려야 한다. 이상을 만드는 일에 다른 사람들도 동참하도록 노력하라. 이것을 통해 그들에게는 우리를 지원할 이유가 생기며 우리에게는 그들의 아이디어를 얻는 혜택이 생긴다. 프로젝트를 위한 핵심적인 지원 인사들을 개별적으로 모집하라. 갈 수 있는 곳이면 어디든 가서, 말만 하지 말고 보여주라. 할 수 있다면, 자신의 아이디어를 시연할 수 있는 시제품(prototype)이나 실물모형(mockup)을 만들라. 시제품은 어느 분야에서든 효력이 있지만 소프트웨어 분야에서는 글로 쓴 어떤 설명보다 훨씬 더 우세하다.

4.3.7 시스템이 자라게 하는 방법

나무의 씨앗에는 어른 나무의 밑그림(idea)이 들어있지만 아직 그 형태나 능력이 완전히 발현되지는 않았다. 싹이 자라고 커진다. 점점 어른 나무를 닮아 가며 점점 더 쓸모 있게 되어 간다. 마침내 열매를 맺고, 그 후에는 죽어서 다른 생물을 위한 거름이 된다.

과장된 표현일 수도 있지만 소프트웨어도 그렇게 취급된다. 다리(bridge)는 그렇지 않다. 미완성 다리는 있어도 '아기 다리'는 없다. 다리는 소프트웨어에 비해 아주 단순하다.

소프트웨어가 자란다고 생각하는 것이 좋다. 그렇게 생각하면 머리 속에 완벽한 그림이 그려지기 전에도 훌륭히 전진해 갈 수 있기 때문이다. 사용자들의 반응을 듣고 소프트웨어의 성장을 바로잡아 줄 수 있다. 약한 가지를 쳐 주는 것도 건강에 좋다.

프로그래머는 전달받아 사용할 수 있는 완성된 시스템을 설계해야 한다. 그런데 고급 프로그래머는 그 이상을 할 수 있어야 한다. 완성된 시스템으로 귀결되는 성장 경로를 설계할 수 있어야 한다. 아이디어의 싹을 가지고 가능한 한 평탄한 경로를 따라 유용한 완성품이 만들어질 수 있게 하는 것이 우리가 할 일이다.

이를 위해서는, 최종 결과를 시각화하고 그것에 대해 기술팀이 흥미를 가질 수 있도록 전달해야 한다. 또한 현재 그들의 위치에서 그들이 원하는 위치까지 가는 경로를 비약 없이 잘 전달해야 한다. 나무는 그 기간 내내 살아 있어야 한다. 어느 순간에 죽었다가 나중에 부활할 수는 없다.

이런 접근 방법은 나선형 개발(spiral development)에 그대로 반영되어 있다. 그 경로에 따라 진도를 표시하기 위해 간격이 너무 멀지 않은 진도표(milestones)를 사용한다. 사업이라는 무한 경쟁의 환경에서는, 비록 잘 설계된 최종 목표와는 거리가 멀다 해도 진도별 배포판(milestone release)을 계속 내면서 최대한 빨리 돈을 버는 것이 상책이다. 프로그래머의 임무 중 하나는, 일정표에 명시되는 성장 경로를 현명하게 선택함으로써 즉각적인 이득과 미래의 이득 사이에 균형을 잡는 일이다.

고급 프로그래머는 소프트웨어와 팀과 개개인의 성장에 대한 3중의 책임이 있다.

독자인 롭 하퍼닉(Rob Hafernik)이 이 절에 대해 다음의 의견을 보내 왔는데, 전문을 인용하는 것이 가장 좋을 것 같다.

여기에서는 그 중요성이 덜 강조된 것 같습니다. 이것은 시스템만의 문제가 아니며, 알고리듬, 사용자 환경(user interface), 데이터 모형(data model) 등의 문제이기도 합니다. 이것은 대형 시스템의 작업을 할 때 중간 목표들을 향해 진도를 맞춰 가기 위해서는 정말로 대단히 중요합니다. 끝까지 다 가서야 전체가 전혀 작동하지 않는다는 사실을 알게 되는 공포 상황만큼 나쁜 것은 없을 것입니다. (보우터 뉴스 서비스(Voter News Service)가 최근 해체된 것을 보십시오. 역자 주: 보우터 뉴스 서비스는 미국 언론사들이 출구 조사를 위해 공동 설립한 기관으로, 2002년 11월 미국 중간 선거를 대비하여 1,000만 달러 이상을 투자하여 시스템을 새롭게 갖추었으나, 선거 당일에 총체적인 문제가 생겨 출구 조사 결과 발표를 포기하고 말았다. 이 기관은 2003년 1월 해체되었다.) 한 걸음 더 나아가 이것은 자연 법칙이라고까지 말하고 싶습니다. 대형의 복잡한 시스템은 무에서 시작하여 구현할 수 없습니다. 의도한 단계를 거쳐 가면서 단순한 시스템에서 복잡한 시스템으로 진화하는 것만 가능합니다.


이 인용글에 대해서는 "빛이 있으라(Fiat lux)!" 하고 응답할 수밖에 없을 것이다.

4.3.8 대화를 잘 하는 방법

대화를 잘 하기 위해서는 우선 그것이 얼마나 어려운 것인지 인식해야 한다. 이것은 기능 자체에 대한 기능이다. 대화할 대상자들이 결점이 있는 사람들이라는 사실 때문에 이 일은 더욱 어려운 일이 된다. 그들은 나를 이해하는 일에 별로 노력을 들이지 않는다. 그들은 말도 잘 못 하고 글도 잘 못 쓴다. 그들은 대개 과로하고 있거나 따분해하고 있으며, 지금 말하고자 하는 큰 문제들보다는 자기 자신의 일에만 초점을 맞추고 있는 것 같다. 개설되어 있는 강좌를 통해 글쓰기, 연설, 듣기 기능을 연습하면, 이것들을 잘 하게 될 때 문제가 어디에 있는지, 그것을 어떻게 고칠 수 있는지 더 쉽게 볼 수 있다는 장점이 있다.

프로그래머는 자기 팀과 대화하는 일에 생존이 달려 있는 사회적 동물이다. 고급 프로그래머는 팀 밖의 사람들과 대화하는 일에 만족이 달려 있는 사회적 동물이다.

프로그래머는 무질서에서 질서를 끌어낸다. 이것을 하는 한 가지 흥미로운 방법은 팀 밖에서 어떤 제안을 시작하게 하는 것이다. 이것은 뼈대(strawman)나 백지 형식으로, 혹은 단지 구두로 시작될 수 있다. 이렇게 이끌어 가는 것은 토론의 조건을 설정한다는 점에서 굉장히 큰 장점이 있다. 이를 통해 나 자신이 비판, 더 나쁘게는 거부와 무시에 내놓인다. 고급 프로그래머는 고유한 권한과 그에 따른 고유한 책임이 있으므로, 이것을 받아들일 각오를 해야 한다. 프로그래머가 아닌 사업가들은 여러 가지 점에서 지도력을 발휘하기 위해 프로그래머가 필요하다. 프로그래머들은 현실에 기초하여 아이디어와 현실을 이어주는 다리의 한 부분이다.

나도 대화를 잘 하는 일에 정통하지는 않지만, 현재 노력하고 있는 것은 네 갈래의 접근 방식이다. 이것은, 아이디어를 정돈하고 충분히 준비를 갖춘 다음, 구두로 이야기를 하고, 사람들에게 백지를 (실제 종이로든, 전자적으로든) 나눠주고, 시연을 하고, 인내심을 가지고 이 과정을 반복하는 것이다. 이런 어려운 대화 과정에서 충분히 인내심을 갖지 않는 때가 많다고 생각한다. 자기 아이디어가 즉각 받아들여지지 않는다고 해서 낙담해서는 안 된다. 그것을 준비하는 데에 노력을 들였다면, 그것 때문에 나를 하찮게 생각하는 사람은 없을 것이다.

4.3.9 사람들에게 듣고 싶어 하지 않는 말을 하는 방법

사람들에게 그들을 불편하게 할 말을 해야 할 때가 있다. 이 일은 어떤 이유가 있기 때문에 하는 것이라는 사실을 기억하라. 그 문제에 대해 아무 것도 할 수 없다 해도, 그들에게 할 수 있는 한 빨리 말해서 그들이 그 사실을 숙지하고 있게 해야 한다.

누군가에게 문제점에 대해 말하는 가장 좋은 방법은 해결책을 동시에 제시하는 것이다. 두 번째로 좋은 방법은 그 문제점에 대해 도움을 요청하는 것이다. 그 사람이 믿지 않을 위험이 있다면, 그 말을 지지해 줄 사람을 모아 봐야 할 것이다.

해야 하는 가장 불쾌하면서 일상적인 말들 중 하나는 ‘예정일을 넘길 것 같군요.’라고 말하는 것이다. 양심적인 프로그래머라면 이런 말을 하기가 싫을 테지만, 그래도 최대한 빨리 해야 한다. 진도 날짜를 지나쳤을 때, 할 수 있는 것이 모든 사람들에게 그 사실을 알리는 일밖에 없다 해도, 대응이 지연되는 것보다 안 좋은 일은 없다. 이런 일을 할 때에는, 물리적으로 같이 하지는 못하더라도, 정신적으로라도 팀으로 같이 하는 것이 더 좋다. 지금의 위치와 그것을 해결하기 위한 일에 대해 팀원들의 의견이 필요할 것이며, 팀원들도 그 결과를 함께 직시해야 할 것이다.

4.3.10 관리상의 신화들을 다루는 방법

신화라는 단어는 허구를 뜻하기도 한다. 하지만 좀 더 깊은 함축이 있다. 이것은 우주에 대해, 그리고 인류와 우주의 관계에 대해 설명하는 종교적으로 중요한 이야기를 뜻하기도 한다. 관리책임자들은 프로그래머로서 배운 것은 잊어버리고, 어떤 신화들을 믿곤 한다. 그 신화들이 거짓이라고 그들에게 설득하려고 하는 것은, 독실한 종교인에게 그들의 믿음에 대한 환상을 깨뜨리려고 하는 것처럼 귀에 거슬리고 성공 가능성이 없는 일이다. 그렇기 때문에 이런 신념들을 신화라고 인식해야 한다.
  • 문서는 많이 만들수록 좋다. (그들은 문서를 원하지만, 그들은 문서를 작성하는 데 시간을 쓰는 것을 원하지 않는다.)
  • 프로그래머들은 다 같다. (프로그래머들도 천차만별로 다르다.)
  • 지연되는 프로젝트에 새로운 자원을 투입해서 진척 속도를 높일 수 있다. (새로 들어온 사람들과 대화하는 데 드는 비용은 거의 항상 도움이 되기보다는 부담이 된다.)
  • 소프트웨어 개발 시간을 확실히 추정하는 것이 가능하다. (이론적으로도 불가능하다.)
  • 프로그래머의 생산성은 코드의 줄 수와 같은 간단한 척도로 측정될 수 있다. (간결한 것을 중시한다면 코드의 줄 수가 늘어나는 것은 좋은 것이 아니라 나쁜 것이다.)

기회가 된다면 이러한 것들을 설명해 볼 수는 있을 것이다. 하지만 실패하더라고 기분나빠 하지 말고, 이런 신화들에 호전적으로 맞서다가 자신의 평판이 나빠지게 하지도 말라. 이러한 신화들 때문에 관리책임자들은 현재 진행되는 프로젝트에 대해서 실제로 통제력을 가지고 있다는 생각을 더욱 굳힌다. 결국 관리책임자가 좋은 사람이라면 도움이 될 것이고, 안 좋은 사람이라면 방해가 될 것이라는 것이 진리이다.

4.3.11 조직의 일시적 혼돈 상태를 다루는 방법

조직이 짧은 시기 동안 큰 혼돈을 겪는 때가 종종 있다. 예를 들어, 근신(layoff), 기업 인수(buyout), 회사 공개(IPO, Initial Public Offering), 해고, 신규 채용 등이 그것이다. 이것은 모든 사람의 마음을 어지럽게 하지만, 자기의 지위가 아닌 능력에 근거하여 개인적 자부심을 가지고 있는 프로그래머는 어지러움이 조금 덜할 것이다. 조직의 혼돈은 프로그래머들이 자신의 마법의 능력을 시험해 볼 수 있는 좋은 기회이다. 나는 이 이야기를 끝까지 아껴 두었다. 이것은 우리 부족(tribe)의 은밀한 비밀이기 때문이다. 프로그래머가 아닌 사람은 이제 그만 읽기를 바란다.

기술자들은 만들고 유지하는 능력이 있다.


전형적인 소프트웨어 회사의 경우, 비기술자들이 주변 사람들에게 지시할 수는 있지만, 기술자 없이는 아무것도 만들거나 유지할 수 없다. 이는 기술자가 일반적으로 제품을 팔거나 사업을 효과적으로 운영할 수 없는 것과 같다. 기술자들의 이 능력은 일시적인 조직의 혼란(mayhem)에 관한 거의 모든 문제들에 대해 버틸 수 있는 힘이 된다. 이런 능력을 가졌다면 이 혼돈을 완전히 무시하고 마치 아무것도 일어나지 않은 것처럼 행동하면 된다. 물론 자신이 해고되는 경우도 있겠지만 이 마법의 능력 때문에 곧 새로운 직장을 찾게 될 것이다. 더욱 흔하게는, 마법의 능력도 없이 스트레스를 받고 있는 사람이 내 자리에 와서 어떤 어리석은 일을 하라고 할 수도 있다. 그 일이 정말로 어리석다고 확신한다면 그 사람 앞에서 그냥 웃으면서 고개를 끄덕이고 회사를 위해서 가장 좋다고 생각되는 다른 일을 하는게 최선이다.

만약 팀장이라면, 팀원들에게도 같은 식으로 하라고 말해 주고, 다른 누가 무슨 말을 해도 무시하라고 말해 주라. 이런 행동 방식이 나 개인에게도 최선이고, 회사와 프로젝트에도 최선이다.

5 참고 문헌

5.1

<Rules00> Guy Kawasaki, Michelle Moreno, and Gary Kawasaki. 2000. HarperBusiness. Rules for Revolutionaries: The Capitalist Manifesto for Creating and Marketing New Products and Services.

<RDev96> Steve McConnell. 1996. Microsoft Press. Redmond, Wash. Rapid Development: Taming Wild Software Schedules.

<CodeC93> Steve McConnell. 1993. Microsoft Press. Redmond, Wash. Code Complete.

<XP99> Kent Beck. 1999. 0201616416. Addison-Wesley. Extreme Programming Explained: Embrace Change.

<PlanXP00> Kent Beck and Martin Fowler. 2000. 0201710919. Addison-Wesley. Planning Extreme Programming.

<Prag99> Andrew Hunt, David Thomas, and Ward Cunningham. 1999. 020161622X. Addison-Wesley. The Pragmatic Programmer: From Journeyman to Master.

<Stronger> Friedrich Nietzsche. 1889. Twilight of the Idols, "Maxims and Arrows", section 8.

5.2 웹 사이트

<PGSite> Paul Graham. 2002. 그의 사이트에 있는 논설(article)들: http://www.paulgraham.com/articles.html 모두 읽을 만하지만, 특히 "Beating the Averages".

<Hacker> Eric S. Raymond. 2003. How to Become a Hacker. http://www.catb.org/~esr/faqs/hacker-howto.html.

<HackDict> Eric S. Raymond. 2003. The New Hacker Dictionary. http://catb.org/esr/jargon/jargon.html

<ExpCS> Edsger W. Dijkstra. 1986. How Experimental is Computing Science? http://www.cs.utexas.edu/users/EWD/ewd09xx/EWD988a.PDF

<Knife> Edsger W. Dijkstra. 1984. On a Cultural Gap. http://www.cs.utexas.edu/users/EWD/ewd09xx/EWD913.PDF

6 역사 (2003년 5월 현재) / History (As Of May, 2003)

6.1 피드백 및 확장 요청 / Request for Feedback or Extension

이 에세이에 대한 의견이 있다면 나에게 보내주십시오. 나는 모든 의견을 반영하려고 노력하고 있으며 많은 수의 의견들이 이 에세이를 발전시켰습니다.

이 에세이는 GNU Free Documentation License를 따릅니다. 이 라이센스는 에세이에만 적용되는 것이 아닙니다. 에세이는 보통 한 사람의 하나의 관점에 바탕을 두고 쓰여져서 특정한 주장에 집착하고 그것을 확신시키려는 경향이 있습니다. 나는 이 에세이가 쉽고 즐겁게 읽힐 수 있었으면 합니다.

또한 나는 이것이 교육적이었으면 합니다. 비록 교과서는 아닐지라도 이것은 많은 단락으로 나뉘어 있어서 쉽게 새로운 단락이 추가될 수 있습니다. 너무 한쪽 시각으로 편중되었다고 생각한다면 올바르다고 생각되는 쪽으로 이 에세이에 추가하십시오. 이것은 이 라이센스의 목적이기도 합니다.

이 문서가 확장될 가치가 있다고 생각하는 것이 너무 잘난 척 하는 것 같기도 하지만, 이것이 영원히 진화됐으면 합니다. 이것이 다음과 같은 방식으로 확장된다면 기쁘겠습니다.
  • 쉽게 읽을 수 있는 목록이 각 단락에 추가됨.
  • 더 많은, 더 좋은 단락이 추가됨.
  • 비록 단락 단위로 번역되는 한이 있더라도 다른 언어로 번역됨.
  • 틀린 점이나 보충할 것이 추가됨.
  • 팜(PDA의 한 종류) 문서나 더 나은 HTML 같이 다른 문서 형식으로 변환될 수 있게 됨.

만약 이러한 일을 나에게 알려 준다면 이 라이센스(GFDL)의 목적에 따라서 다음 버전에 추가하겠습니다. 물론 이 라이센스에 나와 있는 대로, 나에게 알리지 않아도 이 문서에 대한 자기만의 버전을 만들 수 있습니다.

감사합니다.

로버트 L. 리드(Robert L. Read)

6.2 원본 / Original Version

이 문서의 원본은 로버트 L. 리드(Robert L. Read)에 의해 2000년부터 시작되었고 2002년에 사미즈다트(Samizdat) 출판사에서 최초로 전자 문서로 출판되었다.(http://Samizdat.mines.edu) Hire.com의 프로그래머들에게 이 문서를 바친다.

2003년 슬래쉬닷(Slashdot)에서 이 기사가 언급되었고 약 75명의 사람들이 제안과 수정할 것들을 이메일로 나에게 보내왔다. 그것에 감사한다. 비록 많은 중복이 있지만 다음에 열거된 사람들은 커다란 제안을 했거나 문제점을 고치도록 도움을 준 사람들이다. 모건 맥과이어(Morgan McGuire), 데이빗 메이슨(David Mason), 톰 뫼르텔(Tom Moertel), 슬래쉬닷의 닌자 프로그래머(Ninja Programmer) (145252), 벤 비어크(Ben Vierck), 롭 하퍼닉(Rob Hafernik), 마크 하우(Mark Howe), 피터 파라이트(Pieter Pareit), 브라이언 그레이슨(Brian Grayson), 제드 A. 쇼어(Zed A. Shaw), 스티브 벤즈(Steve Benz), 막심 이오프(Maksim Ioffe), 앤드류 우(Andrew Wu), 데이빗 제쉬키(David Jeschke), 톰 코코런(Tom Corcoran).

마지막으로 크리스티나 밸러리(Christina Vallery)에게 감사를 드린다. 그의 수정과 사려깊은 읽기를 통해서 두번째 원고가 크게 발전했다. 그리고 웨인 알렌(Wayne Allen)에게 이 일을 시작하도록 격려해 준 것에 대해서도 감사 드린다.

6.3 원저자의 경력 / Original Author's Bio

로버트 L. 리드(Robert L. Read)는 미국 텍사스주 오스틴에서 부인과 두 아이와 함께 살고 있다. 현재 Hire.com에서 4년 동안 총수석 엔지니어로 일하고 있다. 그전에는 제지 산업 분야에서 스캐너로부터 이미지를 읽어 그것의 품질을 조절해주는 도구를 생산하는 4R Technology를 설립하기도 했다.

그는 1995년 텍사스 주립 대학에서 데이터베이스 이론에 관한 연구로 전산 과학 박사 학위를 받았다. 1987년에는 라이스(Rice) 대학에서 전산 과학 학사 학위를 받았다. 그는 16세 때부터 직업 프로그래머로 일하고 있다.


원저자가 보내온 편지
Subject: RE: Your essay translated into Korean!
Date: Wed, 7 Jan 2004 09:28:48 -0600
From: <Read@hire.com>
To: <*******>

	Thank you!  I am thrilled and honored that this would be done.
I hope it benefits some Korean speakers who do not read English well
enough to enjoy the original.
	Unfortunately, I do not speak Korean---yet.  I do plan to study
it before I reach old age, but there are several other languages that 
I would like to master first.
	It is my understand that the Korean writing sysem (Han Gul?) is
a great acheivement of human invention and the Korean people.


-----Original Message-----
From: *******
Sent: Wednesday, January 07, 2004 7:49 AM
To: Rob Read
Subject: Your essay translated into Korean!


Thank you for your insightful essay, "How to be a Programmer."

Finally it's been translated into Korean on a Wiki site. You can read
it, if you can read Korean ;-), on
http://wiki.kldp.org/wiki.php/HowToBeAProgrammer
Posted by 1010
98..Etc/Etc...2008. 8. 12. 22:24
반응형
We Solve Password Problems

Forgot your password? Need to regain access to password-protected files or systems? Passwords list destroyed?
Passware software recovers or resets passwords for Windows, Word , Excel, QuickBooks, Access, PDF and more than 100 document types.

Windows Key
Quickly and easily reset Windows login passwords in a matter of minutes – no need to reinstall the system.
Read MoreDownloadBuy Now
 
     
Office Key
Recovers all types of passwords for MS Office documents: Excel, Word, Outlook, Access, PowerPoint and Visual Basic for Applications (VBA).
Read MoreDownloadBuy Now
 
     
Passware Kit Enterprise
A complete password recovery solution that supports more than 100 document types.
Read MoreDownloadBuy Now
Posted by 1010
90.개발관련문서2008. 8. 12. 17:33
반응형

Q : 파일 다운로드시 한글파일의 경우 다운로드 되지 않거나 글자가 깨집니다.
A : 익스플로러 "도구 > 인터넷 옵션 > 고급 "에서 고급 UTF8옵션 해제하시면 됩니다.

최종수정일자 : 2008.01.18 14:49:11



출처 : http://www.ihelpers.co.kr/programming/reference/index.php
Posted by 1010
반응형

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Vertically and Horizontally Centering a DIV</title>

<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />

<script type="text/javascript" src="/mint/mint.js.php"></script>

<style type="text/css">
body {
 font-size: 12px;
 font-family: arial, helvetica, sans-serif;
 color: #333;
}
p {
 margin: 1em;
}
.comments {
 background-color: #e3e3e3;
 border-top: 1px solid #ccc;
 border-bottom: 1px solid #ccc;
 padding: 2px;
}

#mydiv {
 position:absolute;
 top: 50%;
 left: 50%;
 width:30em;
 height:18em;
 margin-top: -9em; /*set to a negative number 1/2 of your height*/
 margin-left: -15em; /*set to a negative number 1/2 of your width*/
 border: 1px solid #ccc;
 background-color: #f3f3f3;
}

</style>

</head>
<body>
<div id="mydiv">
 <p>This is a vertically and horizontally centered &lt;div&gt; tag. Try resizing your browser.</p>
 
 <div class="comments">
  <p><strong>Comments:</strong></p>
  <p>Tested in: <span style="color: darkblue">Firefox, IE6, Opera 7, NN4.7, NN7, and Mozilla 1.2.</span><br />
  Works in: <span style="color: darkblue">Firefox, IE6, Opera 7, NN7, and Mozilla 1.2.</span><br />
  Doesn't work in: <span style="color: darkblue">NN4.7</span></p>
  <p>&copy; Copyright 2003, <a href="/">Infinity Web Design</a></p>
 </div>
</div>
</body>
</html>

Posted by 1010
반응형

Table 줄바꿈

  1. 강제로 줄바꿈(특수문자제외) style="word-break:break-all"
  2. 강제로 줄바꿈(특수문자포함) style="word-wrap:break-word"
  3. 줄바꿈 못하도록 nowrap
Posted by 1010
반응형

1. 소스

<html>
<head>
<title>Bizest 체험관 - KIS</title>
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr">
<link rel="alternate" type="application/rss+xml" href="/rss/" title="RSS feed for Bizest KIS"/>
<link rel="stylesheet" type="text/css" href="/skin/x4/css/blue.css" />
<link rel="stylesheet" type="text/css" href="/skin/x4/css/layout.css" />
<link rel="stylesheet" type="text/css" href="/skin/x4/css/form.css" />
<link rel="stylesheet" type="text/css" href="/skin/x4/css/form.css" />
<link rel="stylesheet" media="print" type="text/css" href="/skin/x4/css/print.css" />

print.css

body {
 
 /*
 font-family:"Times New Roman", serif;
 font-size:12pt;
 */

 background:none;

 margin-left: 20px;
 margin-top: 10px;
 margin-right: 20px;
 margin-bottom: 10px;
}

#container {
 width:100%;
}

#container_head {
 DISPLAY: none;
}

#container_body .left {
 DISPLAY: none;
}

#container_body {
 width:100%;
}

#container_body .right {
 width:100%;
}

#container_footer {
 DISPLAY: none;
}



출처 : http://www.ihelpers.co.kr/programming/tipntech.php?CMD=view&TYPE=0&KEY=&SC=S&&CC=&PAGE=1&IDX=639
Posted by 1010
반응형
1. JavaScript 최적화 도구
2. CSS 최적화 도구
3. 웹 사이트 성능 개선

출처 : http://www.mimul.com/pebble/default/2008/01/29/1201616760000.html

Posted by 1010
61.Linux2008. 8. 12. 17:08
반응형

사용자 생성 및 계정 관리

1. 계정 조회

현재 시스템에 로그인된 사용자 계정을 조회 / 사용자 계정에 대한 정보를 확인

          cat –n /etc/passwd

                      root  :  x  :  o  :  o  :  root  :  /root  :  /bin/bash

                        1    2    3    4      5        6          7

                                    1 : 사용자명
                                    2 : 패스워드 (/etc/shadow 파일에 암호화되어 있음)
                                    3 : 사용자 계정 uid
                                    4 : 사용자 계정 gid
                                    5 : 사용자 계정 이름 정보
                                    6 : 사용자 계정 홈 디렉토리
                                    7 : 사용자 계정 로그인 셀

 

            cat –n /etc/shadow

                          root  :  #$%!234^x13  :  11535  :  o  :  99999  :  7  :  :  :  :

                            1            2            3      4      5      6  7  8  9

                                        1 : 사용자명
                                        2 : 패스워드
                                        3 : 패스워드 파일 최종 수정일
                                        4 : 패스워드 변경 최소일
                                        5 : 패스워드 변경 최대일
                                        6 : 패스워드 만료 경고 기간
                                        7 : 패스워드 파기 기간 (패스워드 파기 후 계정 비활성 기간)
                                        8 : 계정 만료 기간
                                        9 : 예약 필드


2. 계정 생성 및 암호 설정

useradd  생성할 계정명

passwd  생성한 계정명

useradd [옵션] 로그인 계정

            -c comment : 사용자 이름 또는 정보
            -d home_directory : 사용자 계정 홈 디렉토리
            -e expire_date : 사용자 계정 유효 기간
            -f inactive_time : 비활성 기간
            -g initial_group : 기본 그룹
            -G grout : 다음 그룹
            -s shell : 기본 로그인 셀
            -u uid : 사용자 계정 uid

 
3. 계정 변경

usermod [옵션] 로그인 계정

-c comment : 사용자 이름 또는 정보
-d home_directory : 사용자 계정 홈 디렉토리
-e expire_date : 사용자 계정 유효 기간
-f inactive_time : 비활성 기간
-g initial_group : 기본 그룹
-G grout : 다음 그룹
-s shell : 기본 로그인 셀
-u uid : 사용자 계정 uid

                      usermod –d /home/user –m user
                      usermod –e 2003-04-05 user
                      usermod –f 3 user
                      usermod –g users user


4. 계정 삭제

userdel –r 계정 (-r : 해당 계정자의 홈디렉토리까지 한 번에 삭제)

 
5. 그룹조회

cat –n /etc/group

 
6. 그룹생성

groupadd [-g GID [-o]]            그룹 id (-o : GID 499이하 값으로 지정)

                      [-r]                    그룹 id 499이하 값으로 자동 지정
                      [-f]                    강제로 생성

                          groupadd –g 900 toheart (900 – groupid / toheart – 그룹명)

 
7. 그룹변경

groupmod [-g gid [-o]]            gid변경

                        [-n]                    새로운 그룹명으로 변경

                                    groupmod –g 700 toheart
                                    groupmod –n kkum toheart


8. 그룹삭제

groupdel group            group 제거

출처 : http://cafe.naver.com/frody.cafe?iframe_url=/BoardRead.do%3Farticleid=11

Posted by 1010
반응형

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> obxGetColor, obxStickGraph</title>
<meta http-equiv="content-type" content="text/html; charset=euc-kr" />
 <script>
 IE=(window.showModalDialog) ? true : false;

 function obxGetColor(color,gap) {

  var rtn='',col,tmp;

  for(var x=0;x <6; x+=2) {
   col=parseInt(color.substr(x,2),16)+gap;
   if (col > 255) col = 255;
   else if (col < 0) col=0;

   if(col < 10) rtn+='0'+col.toString(16);
   else rtn+=col.toString(16);
  }

  return rtn;
 }


 function obxStickGraph(dsize) {

  this.step=3;
  this.speed=10;
  this.total=0;
  this.max=0;
  this.dsize=dsize;
  this.statictext=false;

  this.item= new Object();

  this.add = function (id,size,color,text) {
   this.total += size;
   this.max=Math.max(this.max,size);
   this.item[id]= {'size' : size, 'color' : color.replace('#',''), 'text' : text}
  }

  this.draw = function (id,action) {

   var dColor=obxGetColor(this.item[id].color,-20);

   document.write("<div></div>");
   //this.item[id].div=document.body.appendChild(document.createElement('div'));
   var divs=document.getElementsByTagName('div');
   this.item[id].div=divs[divs.length-1];

   this.item[id].div.style.borderLeft="1px solid #"+dColor;
   this.item[id].div.style.overflow="hidden";
   this.item[id].div.style.height="11px";

   if(!action) this.actDraw(id);
   else this.actDraw(id,5);

  }

  this.getCss= function(width,height,color,bwidth,bcolor) {
   if(width<1)width=1;
   return "overflow:hidden;width:"+width+";height:"+height+";background-color:"+color+";border-right:"+bwidth+"px #"+bcolor+" solid";

  }

  this.actDraw= function(id,limit) {

   var pp = this.item[id].size / this.total;
   var sizep = this.item[id].size / this.max;

   if(!limit) var size=this.dsize * sizep;
   else size=limit;

   var hit = Math.round(this.max/this.dsize*size);
   var percent =(Math.round(this.max/this.dsize*size)/this.total*100).toString().match(/[0-9]*(?:\.[0-9][0-9])?/);

   var text = (this.item[id].text) ? this.item[id].text.replace('#',hit).replace('$',percent) : percent + " %"


   var Color=this.item[id].color;
   var hColor=obxGetColor(Color,20);
   var hhColor=obxGetColor(Color,30);
   var dColor=obxGetColor(this.item[id].color,-20);
   var add='',d;

   if(IE || opera) d=new Array(3,2,1);
   else d=new Array(3,4,5);

   this.item[id].div.innerHTML= ''
    +'<div style="'+this.getCss(size-d[0],1,hColor,1,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[1],2,hColor,3,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[2],2,hColor,5,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[2],1,hhColor,5,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[2],2,Color,5,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[1],2,Color,3,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[0],1,dColor,1,hhColor)+'"></div>'
    +'<span style="position:relative;top:'+(-11-IE)+'px;left:'+(((this.statictext)?this.dsize*sizep:size)+5)+'px;font:7pt verdana">'+text+'</span>'

   if(limit)
   if(this.dsize * sizep > limit) {
    obxStickGraphRunObject=this;
    setTimeout('obxStickGraphRunObject.actDraw("'+id+'",'+(limit+this.step)+');',this.speed);
   }else{
    setTimeout('obxStickGraphRunObject.actDraw("'+id+'");',this.speed);
   }

  }


 }


 grp=new obxStickGraph(200) ;
  grp.add('id1',100,'F8CF7B');
  grp.add('id2',200,'#D1D3D3');

  grp.add('id3',500,'FDB480','# hit ($ %)');
  grp.add('id4',300,'3399ff','# hit');
 </script>
 <style>
  td {font: 11px gulim}
 </style>

</head>


<body>

<table>
<tr>
 <td>
  항목 1
 </td>
 <td width="300">
  <script>grp.draw('id1',true);</script>
 </td>
 <td>
  동적 드로우, 기본출력
 </td>
</tr>

<tr>
 <td>
  항목 2
 </td>
 <td>
  <script>grp.draw('id2');</script>
 </td>
 <td>
  정적 드로우, 기본출력
 </td>
</tr>

<tr>
 <td>
  항목 3
 </td>
 <td>
  <script>grp.draw('id3',true);</script>
 </td>
 <td>
  동적 드로우, "# hit" 출력
 </td>
</tr>

<tr>
 <td>
  항목 4
 </td>
 <td>
  <script>grp.draw('id4');</script>
 </td>
 <td>
  정적 드로우, "# hit" 출력
 </td>
</tr>

</table>


<xmp>

생성 :

 <script>

  grp=new obxStickGraph(200) ;
  //젤 긴게 200px 만하게 grp란걸 만든다 (그래프 길이"만" 입니다, 글자 때문에 더 깁니다.)

  grp.add('id1',100,'F8CF7B');
  //그 grp로 명령을 내립니다.
  // "id1" 아이디로 100이란 값을 넣어준다 이때 색은 F8CF7B로
  //색은 조금 어두운 부분(그래프 밑부분)색입니다

  grp.add('id2',200,'#D1D3D3');

  grp.add('id3',500,'FDB480','# hit ($ %)');
  // 4번째 인자는 출력 문잡니다. #은 값으로 $는 퍼센트값으로 바뀝니다

  grp.add('id4',300,'3399ff','# hit');

 </script>


출력 :

 <script>

  grp.draw('id1',true);
  //아이디에 해당되는 그래프를 출력합니다,
  //이때 두번째 인자는 옵션이고 true로 해주면 동적으로 그립니다.(비추-_-;)

 </script>


동적옵션코드 :

 <script>
  grp.step = 3;
  grp.speed = 10;
  //그려주기(grp.draw()) 전에 이런 코드를 넣어주셔도 됩니다.
  //동적으로 그릴때 쓰는 옵션인데
  //스텝은 한번에 늘어나는 길이(픽셀)고, 스피드는 한번 돌아가는;; 속도입니다. 적을수록 빠르고 0이 최소값입니다.

 +yser님의 조언

  grp.statictext=true
  //텍스트 표시를 정적으로 합니다.
 </script>


</xmp>




</body>
</html>

Posted by 1010
90.개발관련문서2008. 8. 12. 16:15
반응형

00 오른쪽 마우스 메뉴에 도스창 열기 추가하기

원문 : http://kkamagui.springnote.com/pages/392898

참고 : http://zextor.tistory.com/2669790

 

들어가기 전에...


1.추가 방법

DOS 시절부터 컴퓨터를 이용하였거나 프로그래머 개발자의 경우 아직도 Dos이용하고 있습니다.

하지만 DOS를 실행하고 원하는 폴더에 접근하기 위해서는 원도우키 + R  또는 [시작]→[실행] 후 “ CMD “ 를 입력 후 도스창이 실행되면 CD 명령어를 이용하여 원하는 폴더에 이동할 수 있습니다.

레지스트리를 수정하여 탐색기를 이용하여 먼저 가고자 하는 폴더에 접근 후 DOS를 실행 하여 바로 해당 폴더의 경로로 연결되어 사용하기 편리합니다.


1. [시작]→[실행]에서 “regedit “를 입력하고 레지스트리 편집기를 실행한 후, 다음 키 값을 찾는다. HKEY_CLASSES_ROOT\Directory\shell

2. Shell 키 위에서 마우스 오른쪽 마우스 클릭(또는 shell 키 선택 후 오른쪽 공백에서 오른쪽 마우스 클릭) 후 [새로 만들기(N)] → [키]를 선택합니다.

3. 새로운 키의 이름을 DOS(이름은 원하시는 이름으로 하셔도 됩니다.)로 수정합니다.

4. 만들어진 DOS 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 오른쪽 마우스에 표시될 이름을 입력하여 주십시오. ( 예를 들어 도스창이라 입력합니다. )

5. 다음 새로 만들어진 DOS에서 Shell 과 마찬가지로 새로운 키를 만들어 Command 이름으로 수정합니다. ( DOS 와는 달리 반드시 command 이름으로 하여야 합니다. )

6. 만들어진 Command 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 cmd.exe /k cd "%1" 이라는 문자열을 입력하여 주십시오.

7. 컴퓨터를 재 시작하여 탐색기 실행 후 가고자 하는 폴더를 선택 후 오른쪽 마우스 클릭 후 도스창을 클릭하시면 해당 폴더의 경로로 도스창이 열리는 것을 확인 할 수 있습니다.


본 자료는 (주)웰비아닷컴 의 커뮤니티 - 활용팁 에서 스크랩한 것입니다.


이 자료 외에도 많은 정보가 있으니 필요하신 분은 직접 방문해 보시길 바랍니다.

본 페이지에서는 재부팅 후에 사용할 수 있다고 기술하였지만 바로 사용하고 싶으시면 아래와 같이 해주시면 됩니다.


1. '시작' 이 있는 작업표시줄에 마우스 오른쪽 버튼을 눌러 작업관리자를 띄움니다.

2. 프로세스 탭에서 explorer.exe 를 선택하여 프로세스 끝내기를 합니다. 경고가 뜰 경우 그냥 "예" 를 선택하십시오.

3. 그럼 밑에 작업표시줄이 없어질 것 입니다.

4. 그럼 아까 작업관리자의 응용 프로그램 탭으로 이동한 후 새 작업을 클릭합니다.

5. 열기 옆에 있는 입력창에 "explorer" 를 입력합니다.

6. 이제 탐색기의 오른쪽 메뉴에 DOS를 사용할 수 있습니다.

Posted by 1010
반응형

Network Monitor (Netmon)는 네트워크 프로토콜 트래픽 분석 유틸리티입니다.
Network Monitor 3.1 을 사용해서 네트워크 프로토콜을 수집하는 몇 가지 방법을 정리하였습니다.

Netmon 2.x와 3.x의 가장 큰 변화는 캡쳐 필터링이라고 생각합니다.
캡쳐 필터링 옵션 변화에 따른 패킷 수집 방법을 사례 별로 아주 기본적인 테스트를 해 봅니다.

일반적으로 Netmon 트래픽을 수집하기 위해서는 Client와 Server 측에서 함께 로그 수집을 해야
문제 해결을 위한 보다 정확한 데이터를 얻을 수 있습니다.


아래 그림은 Netmon 3.1 인터페이스입니다.
Catpure Filter 제어, Frame Summary, Frame Details, Hex Details 값을 바로 확인할 수 있습니다.

Capture Filter 에 Filter 구문을 작성한 뒤 반드시 Verify, Apply 하여 체크 및 필터를 적용해야 합니다.

사용자 삽입 이미지




























[환경]
Server : 192.168.0.100
Client : 192.168.0.133


Case 1. 특정 Source IP Address(192.168.0.133) 에서 유입되는 Packet 확인

IPv4.SourceAddress = 192.168.0.133

Client 에서 Server 로 터미널 서비스 접속을 시도 하였습니다.

사용자 삽입 이미지


























Case 2. TCP Source Port 가 3389인 frame 확인

Tcp.Port == 3389

Client 에서 Server 로 터미널 서비스 접속을 시도 하였습니다.

사용자 삽입 이미지


























Case 3. 3389 port Packet은 캡쳐하지 않음

Tcp.Port != 3389

터미널 서비스 3389 포트 커넥션을 시도 하였으나 아래 그림과 같이 캡쳐되지 않습니다.

사용자 삽입 이미지



























Case 4. ARP Packet 찾기

ARP
사용자 삽입 이미지



























Case 5. Source Port 1096, Destination Port 3389 과 일치하는 Packet

Tcp.SrcPort == 1096 AND Tcp.DstPort == 3389
사용자 삽입 이미지



























Case 6. Command 명령을 이용한 Packet 수집
C:\Program Files\Microsoft Network Monitor 3\> nmcap /network * /capture /File client.cap:10M

수집이 완료되면 Ctrl + C 를 입력하여 수집을 중지합니다.

사용자 삽입 이미지
























자, 수집을 하셨으니 이제 분석을 해야겠죠?
누가하죠? 어떻게?.... What?
약은 약사에게 패킷 분석은 과장님에게 ^^;


[참고자료]
Microsoft Network Monitor 3.1
http://www.microsoft.com/downloads/details.aspx?familyid=18b1d59d-f4d8-4213-8d17-2f6dde7d7aac&displaylang=en

The Basics of Reading TCP/IP Traces
http://support.microsoft.com/kb/169292/en-us

Explanation of the Three-Way Handshake via TCP/IP
http://support.microsoft.com/kb/172983/en-us

Into to Filtering with Network Monitor 3.0
http://blogs.technet.com/netmon/archive/2006/10/17/into-to-filtering-with-network-monitor-3-0.aspx


작성자 : Lai Go / 작성일자 : 2008.07.01

Posted by 1010
카테고리 없음2008. 8. 12. 15:30
반응형
작성자 : 기술지원부 김 삼 수 <kiss@nextline.net>
 
아파치 2.X 버전에서 mod_cband를 이용한 트래픽관리
 
mod_cband 이란?
Apache에서 개별홈페이지의 일hit수 제한 및 트래픽을 관리하기 위해 사용하는 모듈로서apache 2.x버전에서 사용할 수 있으며 apache를 Dos방식과 Static방식 중 어느 방식으로 설치했느냐에 따라 적재방법 또한 다릅니다. DOS방식의 mod_cband 모듈적재 및 설정 방법을 알아보도록 하겠습니다.
 
[주요기능]
Apache2용 가볍운 트래픽제한 모듈
   * 사용자별 대역폭제한 기능
   * 가상호스트별 대역폭 제한 기능
   * 목적지별 대역폭 제한 기능
   * 제한기능:
         o 모든사용자 대역폭 제한
         o 다운로드 속제 제한
         o 초당요청수 제한
         o 아이피대역별 제한
   * Support for virtualhosts
   * Support for defined users
   * 제한결과 웹을 통한 확인 (/cband-status)
   * 각 사용자별 제한 결과 확인(/cband-status-me)

 
( 1 ) 다운로드

http://freshmeat.net/redir/mod_cband/60304/url_tgz/mod-cband-0.9.7.5.tgz
http://cband.linux.pl/downloads
 
리눅스 쉘 명령어
 
① tar zxvf mod-cband-0.9.7.5.tgz 파일을 다운로드 합니다.
wget은 웹에서 자동적으로 파일을 받아오는데 사용되는 유틸리티이며 HTTP, HTTPS, FTP 프로토콜을 지원합니다.
[root@nextline bin]# wget
http://freshmeat.net/redir/mod_cband/60304/url_tgz/mod-cband-0.9.7.5.tgz
 

 
( 2 ) 압축해제
 
[tar 명령어 옵션]

tar 명령어는 파일을 묶거나 풀 때 사용되는 리눅스 명령어 입니다.
c : tar 파일을 생성할 때 사용합니다.(여러 개의 파일을 하나의 파일로 묶을 때)
v : 묶을 때나 풀어줄 때 파일들의 내용을 자세하게 보여줍니다.
z : gzip과 관련하여 압축이나 해제를 한꺼번에 하려고 할 때 사용합니다.
x : 주어진 이름의 파일에 대하여 추출합니다.
사용법 : tar [옵션] 파일명
 
리눅스 쉘 명령어

① 다운로드된 mod-cband-0.9.7.5.tgz 파일의 압축을 해제 합니다.
[root@nextline bin]# tar zxvf mod-cband-0.9.7.5.tgz
 


( 3 ) 컴파일
 
리눅스 쉘 명령어

① 압축 해제한 mod-cband-0.9.7.5 디렉토리로 이동 합니다.
[root@nextline bin]# cd mod-cband-0.9.7.5

② ./configure 명령을 실행합니다.
./configure 명령은 프로그램을 설치하기 위해 환경설정을 하는 것으로 ./configure 후Makefile파일이 생성됩니다. 모듈을 적재하기 위해 컴파일을 합니다.
[root@nextline mod_throttle-3.1.2]# ./configure
 

 
( 4 ) make
 
리눅스 쉘 명령어

① make 명령을 실행합니다.
make 명령은 대상 디렉토리의 Makefile이라는 이름을 가지고 있는 파일을 보고, 거기에 설정되어 있는 컴파일 명령을 shell을 통해서 실행하는 명령입니다. ./configure 작업에 의해 생성된 Makefile을 참조하게 되며 소스코드를 실제로 컴파일해서 bibary 파일을 생성합니다.
[root@nextline mod_throttle-3.1.2]# make
 
 
 
( 5 ) make install

① make install 명령을 실행합니다.
make 명령에 의해 생성된 binary 파일을 지정된 디렉토리로 이동시켜주며 실제 프로 그램 설치 작업이 이루어집니다.
[root@nextline mod_throttle-3.1.2]# make install
 
 
   
( 6 ) mod_cband.so 파일생성 확인

① ls 명령어를 이용하여 아파치 모듈들이 위치한 modules 디렉토리에 mod_cband.so 파일이 생성 되었는지 확인합니다.
[root@nextline mod-cband-0.9.7.5]# ls /usr/local/apache/modules/
 
 
 
( 7 ) 모듈적재 확인

 
[vi 에디터 사용법]
 
사용형식 : vi [옵션] [생성할 파일명/편집할 파일명]
 
vi 에디터는 입력모드, 명령모드, 실행모드로 구분됩니다.

입력모드 : vi 편집화면에서 문자를 입력할 수 있는 모드로서 입력모드로 진입하기 위해서는 i, a, o, I, A, O, R등이 있습니다. 즉 초기 vi 편집기 모드는 명령어 모드로 진입을 하기때문에 문자를 입력하기 전에 앞의 단축키중 하나를 먼저 입력해야 원하는 문자를 입력할 수 있습니다.
 
명령모드 : 커서이동/문자삭제/문자(열)교체/문자열검색 등을 할수 있는 모드로서 입력모드에서 편집이 완료되면 Esc키를 눌러 명령모드로 진입하면 됩니다.
 
실행모드 : 특별한 명령어를 실행하는 모드로서 명령어모드에서 ":"(콜론)를 누르면 vi 화면 하단 좌측에 vi 특수명령어를 입력할 수 있습니다.
 
실행모드의 일반적으로 쓰이는 특수 명령어
q : 수정 작업이 이루어지지 않은 상태에서 vi 편집기에서 빠져나옵니다.
q! : 수정 작업이 이루어진 부분을 적용시키지 않고 vi 편집기를 강제로 빠져나옵니다.
w : 수정된 작업을 저장합니다.
wq : 수정된 작업을 저장하고 vi 편집기에서 빠져나옵니다.
 
초기 명령어모드 -> 입력모드진입 -> 편집 -> 명령어모드 -> 실행모드 -> 종료
 
① httpd.conf파일에 모듈이 등록되었는지 확인합니다.
[root@nextline mod-cband-0.9.7.5]# vi /usr/local/apache/conf/httpd.conf
LoadModule cband_module         libexec/mod_cband.so
 

 
( 8 ) httpd.conf파일의 메인 환경설정

메인 설정 부분은 가상호스트에도 공통으로 적용되는 설정으로 mod_cband 모듈을 적용시키기 위해 아파치 환경 설정파일인 httpd.conf을 수정합니다.
 
① vi 에디터를 이용하여 httpd.conf파일을 엽니다.
[root@nextline mod-cband-0.9.7.5]# vi /usr/local/apache/conf/httpd.conf
 
② 아파치에 cband 모듈을 적용시키기 위해 다음 라인을 추가 합니다.
 
<IfModule mod_cband.c>
        <Location /cband-status>
                SetHandler cband-status
        </Location>
        <Location /cband-status-me>
                SetHandler cband-status-me
        </Location>
        <Location /~*/cband-status-me>
                SetHandler cband-status-me
        </Location>
        <Location /cband-status>
                Order deny,allow
                Deny from all
                Allow from all
        </Location>
</IfModule>
 
 
  ③ 트래픽 관리자모드 접근 ip설정

<Location /cband-status>
                           Order deny,allow
                           Deny from all
                           Allow from all
        </Location>
 
위 설정은 throttle로 분석된 일hit 및 일 트래픽 제한에 대하여 상황페이지를 볼 관리자페이지에 대한 접속제한 설정입니다. 즉 관리자PC 한곳에서만 분석된 결과페이지를 볼 수 있도록 하려면 아래와 같이 수정합니다.
 
관리자 PC 아이피 : XXX.XXX.XXX.XXX

<Location /cband-status>
                           Order deny,allow
                           Deny from all
                           Allow from XXX.XXX.XXX.XXX
         </Location>

 
( 9 ) 가상호스트 환경설정
 
개별 홈페이지 트래픽 현황 및 관리를 하기 위해서는 httpd.conf <Virtual Hosts>부분에 홈페이지 별로 설정을 하여야 합니다.
 
nextline.co.kr 도메인에 하루에 300M(300*1024*1024byte)의트래픽을 제공하는 설정입니다. bit로 따지면, 2.4Gbit/일 트래픽을 제공하는 것입니다. 만약 하루에 300M를 초과했다면, 503 에러 페이지가 뜨게 됩니다.
 
① nextline.co.kr 도메인을 가진 가상호스트를 추가 하도록 하겠습니다.
 
<VirtualHost xxx.xxx.xxx.xxx>
DocumentRoot /home/nextline/public_html
Servername nextline.co.kr
ServerAlias www.nextline.co.kr
CBandLimit 300Mi
CBandPeriod 1D
</VirtualHost>

 

② httpd.conf 설정 후 적용시키기 위해 apache를 재 시작 시켜 줍니다.
[root@nextline mod-cband-0.9.7.5]# /usr/local/apache/bin/apachectl restart

 
( 10 ) 서버전체 cband 상황보기 (관리자모드)
 
일hit수 일전송량의 제한을 웹브라우즈로 확인하기 위하여 다음과 같은 URL로 확인하도록 하겠습니다. 먼저 서버전체의 제한사항을 관리자가 확인하기 위한 예입니다.
cband 페이지는 기본 15초마다 리플레쉬 합니다.
 
① 주도메인이 nextline.co.kr로 설정된 예입니다.
확인하는 방법 :http://IP주소/cband-status
 

② 개별사이트 cband 상황보기 (사용자모드)
확인하는 방법 : http://nextline.co.kr/cband-status-me
 

 
( 11 )지시자 및 단위설명

  단위
  전송속도 단위
  kbps, Mbps, Gbps - bits per second:1024, 1024*1024 , 1024*1024*1024 bps
  kb/s, Mb/s, Gb/s - bytes persecond:  1024, 1024*1024, 1024*1024*1024 b/s
  기본 : kbps
 
  트래픽 쿼터 단위
  K, M, G - bytes: 1000, 1000*1000,1000*1000*1000 bytes
  Ki, Mi, Gi - bytes: 1024, 1024*1024,1024*1024*1024 bytes
  기본 : K
 
  시간(기간) 단위
  S, M, H, D, W - 초, 분, 시간, 일, 주
  기본 : S
 
  지시자들
  이름 : CBandDefaultExceededURL
  설명 : 제한을 초과했을때보여줄 URL  (지정하지 않으면, 503 에러 페이지)
  문맥 : Serverconfig
  문법 :CBandDefaultExceededURL URL
 
  이름 : CBandDefaultExceededCode
  설명 : 제한을 초과했을시 보여줄 에러코드
  문맥 : Server config
  문법 :CBandDefaultExceededCode HTTP_CODE
  예제 :CBandDefaultExceededCode 509 
 
  이름 : CBandScoreFlushPeriod
  설명 : scoreboard 파일에기록할 요청수, mod_cband 의 성능에 영향을 준다.
  기본값 : 1
  문맥 : Server config
  문법 :CBandScoreFlushPeriod 요청수
  예제 :CBandScoreFlushPeriod 100 (매 100번의 요청에 한번씩 scoreboard 파일에 기록)
 
 
  이름 : CBandSpeed
  설명 : 가상호스트 도메인의 최대 속도,요청수, 접속수  설정
  문맥 :<Virtualhost>
  문법 : CBandSpeed kbpsrps max_conn
       kbps - 초당 최대 전송속도
       rps - 초당 최대 요청수
       max_conn - 최대 동시 접속수
       예제 : CBandSpeed 102410 30
       최대 1024kbps전송속도로 제한, 초당 10개의 요청 처리, 동시 접속을 30개로 제한.
 
  이름 : CBandRemoteSpeed
  설명 : 접속자(IP)의 최대속도, 요청수, 접속수 제한 (접속자당 설정)
  문맥 :<Virtualhost>
  문법 : CBandRemoteSpeedkbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당최대 요청수
        max_conn - 최대 동시 접속수
        예제 : CBandRemoteSpeed20kb/s 3 3
        접속자(ip)에대해 최대 20kb/s , 초당 3개의 요청, 동시 접속 3개로 제한.
 
  이름 : CBandClassRemoteSpeed
  설명 : 정의한 class(ip 범위)에대해 최대속도, 요청수, 접속수 제한
  문맥 :<Virtualhost>
  문법 :CBandClassRemoteSpeed class_name kbps rps
        class_name - 이미 정의한 클래스 이름 (IP범위)
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
  예제 : <CBandClassgooglebot_class>
          CBandClassDst 66.249.64/24
          CBandClassDst 66.249.65/24
          CBandClassDst 66.249.79/24
         </CBandClass>
          CBandClassRemoteSpeedgooglebot_class 20kb/s 2 3
          위에서 정의한클래스(googlebot_class)의 요청에는 20kb/s 의 전송속도,
         초당 3개의 요청, 동시 접속 3개로 제한.
 
  이름 : CBandRandomPulse
  설명 : 속도 제한을 위해서 임의의파형을 생성한 다음 처리하는 mod_cband의 처리 방법이다. 부하가 많을 때는 자동 Off된다.

  문맥 : Global
  문법 : CBandRandomPulseOn/Off
 
  이름 : CBandLimit
  설명 : 제한할 전송량을 설정한다.(기간은 CBandPeriod 에서 설정)
  문맥 :<Virtualhost>
  문법 : CBandLimit limit
        limit - 전송량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
  예제 : CBandLimit 10M
        전송양을 10M(10*1000*1000bytes)로 제한한다.
        CBandLimit 10Mi
        전송양을 10M(10*1024*1024bytes)로 제한한다.
 
  이름 : CBandClassLimit
  설명 : 정의한 class(ip범위)에대해 제한할 전송량 설정.
  문맥 :<Virtualhost>
  문법 : CBandClassLimitclass_name limit
        class_name - 이미 정의한 클래스 이름(ip범위)
        limit - 전송량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
 
  이름 : CBandExceededURL
  설명 : 제한을 초과했을시 보여줄URL, 지정하지 않으면 503 에러 발생
  문맥 :<Virtualhost>
  문법 : CBandExceededURLURL
 
  이름 : CBandExceededSpeed
  설명 : 전송양을 초과했을시 , 전송속도제한 설정.
  문맥 :<Virtualhost>
  문법 :CBandExceededSpeed kbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
  이름 : CBandScoreboard
  설명 : 가상호스트의 scoreboard파일 지정. (성능향상을 위해 필요)
  문맥 :<Virtualhost>
  문법 : CBandScoreboardpath
        (path는 아파치(nobody또는 apache)권한으로 쓰기 가능해야 함)
 
이름 : CBandPeriod
  설명 : 용량제한기간(이 기간이 지나면,측정되었던 용량은 지워진다.)
  문맥 :<Virtualhost>
  문법 : CBandPeriod period
        period - 사용단위: S (초), M (분), H (시간), D (일), W (주)
  예제 : CBandPeriod1W  (1주일)
        CBandPeriod 14D  (14일)
        CBandPeriod 60M  (60분)
 
  이름 : CBandPeriodSlice
  설명 : 기간이 길때는 나눌 기간을명시한다.
  기본값 : slice_len = limit
  문맥 :<Virtualhost>
  문법 : CBandPeriodSliceslice_length
  예제 : CBandLimit 100G
        CBandPeriod 4W
        CBandPeriodSlice 1W
        4주는 1주일 단위로 나뉜다(4W/1W = 4). 용량은 100G/4=25G
        1주에 25G, 2주째 50G 이렇게 나눠 처리 된다.
 
  이름 : <CBandUser>
  설명 : 새로운 cband 가상 사용자설정
  문맥 : Server config
  문법 : <CBandUseruser_name>
 
  이름 : CBandUserSpeed
  설명 : cband 가상 사용자의 속도,요청수, 동시 접속수 제한
  문맥 : <CBandUser>
  문법 : CBandUserSpeedkbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
  예제 : CBandUserSpeed100kb/s 10 5
 
 
  이름 : CBandUserLimit
  설명 : cband 가상 사용자의 저송용량 제한.
  문맥 : <CBandUser>
  문법 : CBandUserLimitlimit
     limit - 사용용량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
  예제 : CBandUserLimit 10M
        CBandUserLimit 10Mi
 
  이름 : CBandUserClassLimit
  설명 : cband 가상 사용자의 정의한class(ip범위)에 대해 제한할 전송량 설정
  문맥 : <CBandUser>
  문법 :CBandUserClassLimit class_name limit
        class_name - 지정한 class(IP범위)이름
       limit -사용용량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
 
  이름 : CBandUserExceededURL
  설명 : cband 가상 사용자의,제한을 초과했을시 보여줄 URL,
        지정하지 않으면 503 에러 발생 ( 가상호스트에서 )
  문맥 : <CBandUser>
  문법 :CBandUserExceededURL URL
 
  이름 : CBandUserExceededSpeed
  설명 : cband 가상 사용자의,전송양을 초과했을시 , 전송속도 제한 설정.
  문맥 : <CBandUser>
  문법 : CBandUserExceededSpeed kbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
 
  이름 : CBandUserScoreboard
  설명 : cband 가상 사용자의,scoreboard 파일 지정.
  문맥 : <CBandUser>
  문법 : CBandUserScoreboard path
        (path는 아파치(nobody또는 apache)권한으로 쓰기가능해야 함)
 
  이름 : CBandUserPeriod
  설명 : cband 가상 사용자의, 용량제한기간(기간이 지나면, 측정되었던 용량은 지워진다.)
  문맥 : <CBandUser>
  문법 : CBandUserPeriodperiod
        period - 사용단위: S (초), M (분), H (시간), D (일), W (주)
  예제 : CBandUserPeriod 1W
        CBandUserPeriod 14D
        CBandUserPeriod 60M
 
  이름 : CBandUserPeriodSlice
  설명 : cband 가상 사용자의,기간을 나눌 기간 명시
  기본값 : slice_len = limit
  문맥 : <CBandUser>
  문법 :CBandUserPeriodSlice slice_length
  예제 : CBandUserLimit100G
        CBandUserPeriod 4W
        CBandUserPeriodSlice 1W
        4주는 1주일 단위로 나뉜다(4W/1W = 4). 용량은 100G/4=25G
        1주에 25G, 2주째 50G 이렇게 나눠 처리 된다.

 
( 12 ) 개별홈페이지 cband 정책 적용 예
 
① 자료실 속도제한
   nextline.net 도메인에 대해서 속도를 1024kbps로 제한하며, 초당 10번의 연결, 동
   시 접속자를 30으로 제한하는 예제입니다.
  
<VirtualHost xxx.xxx.xxx.xxx>
DocumentRoot /home/nextline1/public_html
Servername nextline.net
ServerAlias www.nextline.net
CBandSpeed 1024 10 30
CBandRemoteSpeed 20kb/s 3 30
</VirtualHost>
 
                         
 
 
확인하는 방법 : http://nextline.net/cband-status-me
 
 
② 사용자 일트래픽 제공 및 초과시 연결수 제한 nextline.com 도메인에 대해 하루에 100Mbyte의 트레픽을 제공하며, 100M를 초가했다면, 속도를 128bps로 제한, 초당 5번의 연결, 동시접속자를 15로 제한하는 예제입니다.
 
<VirtualHost xxx.xxx.xxx.xxx>
DocumentRoot /home/nextline2/public_html
Servername nextline.com
ServerAlias www.nextline.com
CBandLimit 100Mi
CBandExceededSpeed 128 5 15
CBandPeriod 1D
</VirtualHost>
 

 
확인하는 방법 : http://nextline.com/cband-status-me

 
   
( 13 ) 그 외 적용 예를 들어보겠습니다.
 
① 한 사용자에 여러 도메인을 운영할 때 입니다.
위 설정은 nextline이라는 가상 사용자를 지%E

Posted by 1010
98..Etc/Etc...2008. 8. 12. 15:09
반응형

준비물:

utra edit - 에디터 프로그램입니다. hex에디트까지 가능하게 해줍니다.
qpst - 꼭 qpst가 아니여도 좋습니다. 핸드폰에 파일을 다운로드, 업로드만 가능하게 해주면 됩니다.
공학계산기 - 윈도우의 계산기나 손에 들고 있는 계산기면 OK. 윈도우의 계산기일경우 보기->공학용 을 찾아서 클릭해주시면 됩니다.


1. 이론

hex란 무엇인가?

사용자 삽입 이미지

hex란 hexagonal 의 단축형으로써 16진법이라는 뜻입니다. 우리들이 흔히 쓰는 10진수는 decimal, 2진수는 binary, 8진수는 octal 이라고 표시합니다.  간단하게 축약해서 16진수-hex, 10진수-dec, 2진수는 bin, 8진수는 oct 입니다.10진수는 0부터 9까지 가 1의 자리를 나타내며, 9에서 1이 더해지는순간 십의 자리에는 1이 일의 자리에는 0이 오는 10이 됩니다. 2진수는 0과 1 뿐입니다. 1에서 1이 더해지는순간 십의 자리에 1이 오고 일의 자리에 0이 와서 10이 됩니다. 즉 0(0), 1(1), 10(2) 입니다. 괄호안은 10진수로 읽었을때입니다. 8진수도 0부터 8까지로 이루어져있습니다. 마지막으로 16진수는 0부터 F까지 이루어져 있습니다. 이는 0,1,2,3,4,5,6,7,8,9,A(10),B(11),C(12),D(13),E(14),F(15)로 이루어져 있는 것입니다. 즉 9라는 숫자에서 1이 더해져도 십의 자리에 1이 오지않고 A가 됩니다. 이후로 B, C, D, E, F로 넘어가서 F에서 1이 더해지는순간 십의 자리에 1이 오고 일의 자리에 0이 와서 10이라는 숫자가 됩니다. 컴퓨터에 데이터가 저장될때에는 2진수와 16진수로 저장이 됩니다. 우리가 2진수는 123라는 숫자를 입력하게 되면 1111011이라는 숫자로 저장이됩니다. 16진수의 경우에는 7B라는 숫자로 저장이 됩니다. 하지만 2진수는 컴퓨터가 빠르게 이해는 할지몰라도 사람이 보고 읽고 이해하기엔(이를 가독성이라고 합니다) 너무 어렵죠. 그때문에 16진수코드를 쓰게 되는겁니다.


게임에서 hex조작은 어떻게 하나?

파일을 hex코드로 열어서 해당관련 숫자를 찾아서 숫자를 바꾸어주시면됩니다. 엉뚱한 코드를 손을 대셨다가 게임이 엉망이 되는 경우도 많으니 조심하셔야합니다. 이제 실습에 들어가보겠습니다.

사용자 삽입 이미지

일단 QPST로 접속을 합니다. 그리고 원하는 게임폴더로 찾아갑니다. 저의 경우에는 제일 만만한 판타지포에버2를 예로 들겠습니다. (참고로 저는 SKT통신사며 쓰이는 핸드폰은 MS500입니다.)

사용자 삽입 이미지


게임이 저장된 forever0.db 파일을 다운받습니다.

사용자 삽입 이미지


만약 ultra edit가 hex로 열지 못한다면 편집->hex기능을 찾아서 클릭해주시면 됩니다.

그리고 게임에 접속해서 케릭터의 골드, 레벨, 경험치, 스킬포인트, 찍은 스킬 등등을 공책이나 컴퓨터의 메모장에다가 잘 적어둡니다.

저의 경우 골드가 15032216골드 입니다. 약 1500만골드군요. 이미 이전에 손을 써두었기에 저런 골드가 가능해졌습니다.

사용자 삽입 이미지


자 15032216이라는 숫자를 계산기에 입력을 합니다. 그리고 이상태로 Hex를 클릭해보세요.

사용자 삽입 이미지


E55F98이라는 숫자로 바뀝니다. 앞으로 숫자는 2개씩 끊어서 보기로 합시다.
E5 5F 98 입니다. 이숫자를 잘 기억해두었다가, 아까 ultra edit로 연 forever0.db파일에서 맞는 숫자를 찾아봅니다.

사용자 삽입 이미지


아! 찾았군요! E5 5F 98 잘보입니다. 이 숫자를 FF FF FF 로 변경하시면 금액이 변경됩니다. FFFFFF라는 숫자는 10진수로 16777215입니다. 즉 약 1600만골드입니다.

이번엔 케릭터의 레벨을 찾아가봅시다. 저의 경우 케릭터의 레벨은 81입니다. 81이라는 dec숫자를 계산기로 통해서 hex로 바꾸면 51이라는 숫자가 됩니다. 이또한 찾아봅시다.

사용자 삽입 이미지


아! 또 찾았군요! 이 숫자를 FF로 바꾸시면 255레벨로 바뀝니다. 하지만 일반적인 게임들이 보통 99레벨이 끝인것을 감안해봐서 99레벨로 바꾸기로 합시다. 99라는 dec숫자를 hex로 바꾸어보면 63입니다. 아까 찾은 51이라는 숫자 대신 63을 바꾸어 적습니다.

그리고 파일을 저장해서 다시 qpst로 핸드폰으로 저장합니다.

그리고 게임을 실행해보시면??

와우!! 놀랍네요. 전부적용이 되어있습니다. 이제 레벨노가다와 골드노가다는 안해도 되겠군요.

기타 제가 찾아서 쓰고 있는 주소들은

골드:0번     b,c,d라인 

레벨: 1번 - 200번 4
      2번 - 240번 7
      3번 - 280번 a

스킬 : 1번 - 230  0 부터 240 0 까지
       2번 - 270  3 부터 280 3 까지
       3번 - 2b0  6 부터 2c0 6

exp : 200 a b
      240 d e
      290 0 1

스킬포인트 : 240 5
            280 8
             2c0 b

입니다.

사용자 삽입 이미지


위에 제가 적은것을 보는방법은 간단합니다. hex코드에서 왼쪽 끝을 보시면 해당 00000000h 라는 주소들과 위에는 0부터 f까지 주소가 있습니다. 이것을 찾아서 보시면 됩니다. 예를 들어 골드의 경우 00000000h의 주소의 b,c,d라인에 있다는 소리입니다.

이것으로 간단한 hex조작을 해보았습니다. 여러분들도 쉽게 하실수 있기를 기대하겠습니다. 만약 내용이 어렵다면 댓글을 주시면 빠르게 확인해서 답을 드리겠습니다

Posted by 1010
반응형
EXE, DLL, SFX 등의 PE 파일을 분석해서 사용한 압축기, 암호화기, 컴파일러 정보를 표시하는 프로그램입니다

PE 파일이 어떤 컴파일러로 컴파일되었고,어떤 패킹 되었는지 알수있게 하는 프로그램입니다.

본 프로그램은 사용에 아무런 제한이 없는 프리웨어입니다.


EXE, DLL, SFX 등의 PE 파일을 분석해서 사용한 압축기, 암호화기, 컴파일러 정보를 표시하는 프로그램입니다. PE 파일 내의 470 개 이상의 서로 다른 기호들을 감지할 수 있습니다.

파일분석, 어셈블링, 디어셈블링 등을 하는 분들께 필요합니다.




  • 알려지지 않은 파일이나 변경된 파일을 감지하기 위한 "Advanced" 감지 모드
  • 쉘 확장, 커멘드 라인 지원, 항상 위에 옵션, 드래그 & 드롭 등의 인터페이스 제공
  • 동시에 여러 파일 또는 여러 디렉터리를 스캔할 수 있음
  • Task Viewer나 컨트롤러 제공
  • Generic OEP Finder나 Krypto ANALyzer 와 같은 플러그인을 사용할 수 있는 인터페이스
  • 발견적(Heuristic) 스캐닝 옵션
  • PE details, Imports, Exports, TLS viewers 제공
  • 빠른 디셈블러 내장
  • 헥스 뷰어 내장

    사용자 삽입 이미지



  • Posted by 카이란
    Posted by 1010
    반응형
    사용자 삽입 이미지
    사용자 삽입 이미지
    사용자 삽입 이미지
    사용자 삽입 이미지
    사용자 삽입 이미지
























    본 프로그램은 사용에 아무런 제한이 없는 프리웨어입니다.

    사용자 PC에 설치된 알수없는 프로그램들의 정보를 제공하며 불필요한 프로그램의 언인스톨을 도와 드립니다.


    제작자 설명
    애니그레이는 사용자 PC에 설치된 프로그램 정보 제공 및 알수 없는 프로그램을 유저가 쉽게 확인 할수 있도록 제공하며 불필요한 프로그램을 손쉽게 삭제 할수 있도록 도와 드립니다.

    인터넷의 발전으로 인터넷 웹서핑이 일반화 되어 있는 요즘, 개인 컴퓨터에는 사용자가 알수 없는  Atcive-X ,기타 설치프로그램 및 여러 상주 프로그램들이 PC상태에 많은 영향을 주고 있습니다.

    이러한 상태에서는 현재 프로그램 가동이 느려지거나 사용이 불가능하기도 합니다. 클린애드 기술연구소에서 개발한 사용자 컴퓨터에 설치된 프로그램정보제공 프로그램 ‘애니그레이(anygray)’ 는 사용자의 컴퓨터에 사용자가 알지 못하는 프로그램에 대한 정보를 제공하고 사용자가 불필요하다고 판단할시 삭제를 할수 있도록 도와드리는 프로그램입니다.

    그리고 [시작프로그램 의뢰하기]라는 기능을 제공하여 사용자가 시작프로그램에 등제된 알수없는 프로그램에대해 특별한 검색 포탈 사이트의 검색이 필요없이 애니그레이를 통해서 정보를 얻을수 있도록 제공합니다.


    주요기능
    사용자 PC의 프로그램 검색기능
    사용자PC에 설치되어 있는 프로그램에 대한 정보를 제공하고 사용자에게 설치된 프로그램의 용도 및 삭제 방법등을
    제공합니다.

    사용자PC에 설치된 프로그램 리뷰
    사용자 컴퓨터에 설치된 프로그램에 대한 리뷰를 제공하여 사용자에게 유익한 프로그램은 더욱더 사용을 권장하도록 유도하고, 그렇지 못한 프로그램의 경우 삭제를 유도함으로써 사용자가 안심하고 프로그램을 사용할수 있도록 합니다.
    앞으로 더욱 더 양질의 리뷰를 제공할 예정입니다.

    프로그램 전체보기 기능
    애니그레이 인터페이스는 사용자에 설치되어 있는 프로그램을 간단하게 볼수 있는 [간단보기기능]과 컴퓨터의 [제어판]의 개념과 같은 [전체프로그램보기 기능]이 있어 인터페이스를 사용지 사용자가 불편함이 없도록 두가지 기능의 인터페이스를 제공하고 있습니다.

    시작프로그램 관리기능
    애니그레이를 통해서 사용자컴퓨터의 시작프로그램관리에 등제된 프로그램을 삭제 또는 관리하실수 있습니다.

    시작프로그램 의뢰하기 기능

    사용자의 시작프로그램에 설치되어 있는 프로그램에 대해 애니그레이에 의뢰함으로서 시작프로그램에 등제된 프로그램에 대한 정보를 얻으실수 있습니다.

    실행프로세스 관리기능

    현재 실행중인 프로세스가 종료가 안될경우 애니그레이를 통해서 종료하실수 있습니다.

    히스토리기능

    사용자가 애니그레이를 사용한 흔적을 히스토리를 통해서 확인하실수 있습니다.

    도움말 기능

    애니그레이를 사용하기위한 기본지식을 얻으실수 있습니다


    주요특징
    애니그레이는 사용자의 컴퓨터에 알수 없는 프로그램에 대한 정보를 제공하고 필요 없을 경우에는 삭제를 유도하는 헬퍼프로그램입니다.
    백신치료프로그램과는 개념이 다르며, 잘모르고 무턱대고 삭제되는 프로그램이나 사용자를 불편하게 하는 프로그램에 대한 정보를 제공하여, 사용자 스스로가 판단하고 삭제 또는 예방을 하도록 유도하는 프로그램입니다.
    최소한 사용자PC의 레지스터리나 기타 시스템관련 파일을 거의 건드리지 않으며, 프로그램자체가 사용자가 편안하게 사용할수 있는 프로그램입니다.
    앞으로 더욱더 연구하여 100% 프리웨어인만큼 사용자에게 유익한 프로그램이 되도록 연구개발하겠습니다.


    실행 및 설치방법
    다운로드 받은 파일을 실행하여 순서에 따라 설치 하시면 됩니다.




    Posted by 1010
    반응형
    네트워크 프로그래밍을 하다보면 송/수신된 패킷을 확인이 필요할 때가 있다.
    이때 주로 사용되는 네트워크 패킷 캡쳐 프로그램일 소개하려 한다.
    SmartSniff 는 일단 무료로 사용가능하며 다른 패킷 캡쳐 프로그램보다 간편하게 이용할 수 있다.

    제작사 홈페이지: http://www.nirsoft.net/

    사용자 삽입 이미지
    Posted by 1010
    90.개발관련문서2008. 8. 12. 14:14
    반응형
    출판일 :2005년 10월호

     패킷 분석기는 네트워크 구축이나 운영시 발생하는 문제를 해결하기 위한 필수 요소다. 때문에 네트워크 관리자라면 누구라도 패킷 분석기의 세상에 한번쯤은 빠져들어야 한다. 패킷 분석기는 스니퍼와 같은 고가의 상용 제품도 있지만 이더리얼과 깉이 누구라도 쉽게 무료로 사용할 수 있는 공개용 솔루션도 있다. 이번호에는 국내뿐 아니라 해외에서도 네트워크 관리자라면 보편적으로 많이 사용하고 있는 이더리얼의 주요 기능과 활용법에 대해 소개한다.

    최성열| 파이오링크 기술지원센터장

     한국의 큰 명절인 지난 추석에 필자는 중요한 일 때문에 가족들과 떨어져 일본에서 보내야만 했다. 신규로 구성하는 네트워크에서 문제가 발생해 일본의 파트너들과 함께 일을 했다. 그곳에서 발생한 문제는 이더넷 패킷이 일부 변경되거나 패킷이 가끔 손실되는 현상이었다. 어떻게 해서든 문제를 찾아야 하는 상황이라 파이어월, 2, 3, 4계층 스위치 등 어느 구간에서 문제가 발생하는지를 찾기 위해 여러 명의 엔지니어들과 함께 일했다.
    보통 패킷을 분석할 때는 예상되는 구간에 한 대 정도만 분석기를 설치하고 수집된 데이터를 기반으로 분석을 하는데, 이번에는 어떤 장비도 믿을 수가 없었기에 각 구간마다 분석기를 설치했다. 덕분에 당시 설치했던 패킷 분석기만 13대였다(사실 필자가 문제 해결을 위해 이렇게 많은 장비를 사용하기는 처음이었다). 이때 사용한 프로그램으로는 상용으로 가장 많이 사용되는 스니퍼(Sniffer)와 공개용으로 많이 사용되는 이더리얼(Ethereal)이었다.
    대부분의 엔지니어들의 노트북에는 이 두 개의 프로그램이 탑재돼 있을 것이며, 어떤 시스템을 사용하는가는 그 구간을 담당하는 엔지니어가 익숙한 정도에 달려 있다.


    패킷 분석기의 역할과 중요성
    며칠 동안 필자가 뚫어지라 바라보았던 이더리얼과 스니퍼를 우리는 패킷 분석기(Packet Analyzer)라고 부른다. 이들 솔루션은 연결된 네트워크에 지나가는 패킷들을 하나도 빠짐없이 모두 그 형태에 맞게 나열해주고 일부 필요한 것들은 통계로 알려준다.
    이 같은 패킷 분석기의 사용은 문제를 해결할 때 뿐만이 아니라 여러분들이 책으로만 봐왔던 네트워크 프로토콜의 실제 동작을 직접 확인하는 용도로 사용할 수도 있으며, 필요에 따라서는 책으로 소개되지 않은 애플리케이션(P2P, 바이러스, 메신저)에 대해서도 쉽게 파악하는 용도로 사용할 수도 있다. 




    패킷 분석기는 (그림 1)처럼 확인하고자 하는 구간에 설치해 패킷들을 캡처하고, 관리자를 이를 기반으로 문제를 분석할 수 있도록 한다. 일반적으로 컴퓨터에서 어떤 통신이 안될 경우에는 해당 컴퓨터에 이더리얼을 설치하거나, 해당 컴퓨터가 설치된 같은 네트워크에 연결해 문제가 있는 컴퓨터와 동일한 동작을 해 볼 수도 있다. (그림 1)에서 무언가를 자세하게 들여보는 듯한 '돋보기’그림이 패킷 분석기라는 툴의 특징을 가장 잘 나타내준다고 할 수 있다.



    (화면 1) 이더리얼 실행 화면


    이더리얼과 같은 패킷 분석기의 값어치는 여러분들이 어떻게 사용하느냐에 따라서 하늘과 땅 차이가 난다. (화면 1)의 이더리얼 실행 화면에서 볼 수 있듯이 짧은 순간에도 상당히 많은 패킷들을 수집하므로 툴에서 제공하는 여러 기능을 적절히 이용할 줄 아는 지혜를 가져야 한다. 그렇지 않으면 짧은 내용의 패킷 분석을 위해서 너무 많은 시간을 들여야 할지도 모른다.
    보통 이더리얼을 처음 접한 사람들은 모두들 큰 그림의 패킷 하나하나를 제일 깊은 단계로 생각을 한다. 하지만 패킷 분석기의 가장 중요한 특징은 바로 패킷 하나를 세부적인 단계로 나눠볼 수 있다는 점이다. (화면 1)을 보고 여러분들이 이미 알아차렸을 수도 있지만 대부분의 툴은 패킷 하나하나의 세부 구조와 정보를 우리가 알기 쉽게 번역해 주고 있다.



    (화면 2) 웹에서 GET을 보낼 때의 패킷 세부 내용


    그동안 필자의 연재를 지속적으로 보아온 독자라면 (화면 2)를 조금만 살펴봐도 별 어려움 없이 내용을 이해할 수 있을 것이다. (화면 2)는 여러 패킷도 아니고 단 하나의 패킷이다. 앞에서 설명했지만 패킷의 리스트(List)를 주욱 살펴봐서 어떤 패킷이 오는지 혹은 오지 않는지를 따지는 것은 아주 기본적인 사항이며, 나아가서는 이렇게 패킷 하나하나를 살펴서 어떤 정보들을 주고받았는지도 살펴야 한다(아마 시간이 지나면 여러분들도 그렇게 될 수 밖에 없다. 왜냐하면 때로는 패킷은 다 왔는데도 통신이 안 되는 경우가 있기 때문이다).


    이더리얼 구하기와 설치하기
    이더리얼은 공개용이므로 홈페이지(www.ethereal.com)에 가면 누구라도 손쉽게 구할 수가 있으며 설치 또한 간단하다. 하지만 주의할 점은 이더리얼 설치 전에 컴퓨터의 LAN 카드로 들어오는 패킷들을 이더리얼로 수집해 주는 Winpcap(www.winpcap.org)을 먼저 설치해야 한다. 이 프로그램이 없으면 이더리얼은 무용지물이다.
    현재 제공되는 이더리얼의 버전은 0.10.12 버전이다. 맨 앞이 1이 아닌 0이어서 불안해 할 수도 있지만 이미 여러 차례 패치가 됐고 많은 사람들이 사용하고 있으므로 실무에 사용해도 전혀 손색이 없다(사실 상용 버전보다는 디자인 측면에서 많이 부족하지만 요즘 나온 버전에는 버튼에 아이콘들도 추가해서 제법 틀을 갖춰가고 있다).


     

    <이더리얼의 탄생 과정>
    이더리얼은 리눅스 계열에서 사용한 패킷 덤프(dump) 프로그램인 'tcpdump'라는 툴에서 시작됐다. tcpdump는 리눅스에서 시스템에서 처리하는 패킷을 다양한 옵션으로 볼 수 있는 명령어이고, 지금도 리눅스 애호가로부터 많이 사용되고 있는 명령어다. 이더리얼은 리눅스에서도 비슷한 화면구조로 돼 있는 GUI 프로그램이며, 필자가 설명하고 있는 것은 이것을 윈도우에서 사용할 수 있도록 돼 있는 프로그램이다. 이더리얼 홈페이지를 자세히 살펴보면 깜짝 놀랄 세 가지 일이 있다.
    첫번째는 현재 이더리얼은 719개의 다양한 프로토콜을 지원한다는 점이다. 이 세상에 얼마나 많은 프로토콜이 존재하는지는 필자도 잘 모르지만, 실무에서 사용하고 있는 프로토콜 대부분은 아마도 이더리얼이 지원할 것이다(프로토콜이 지원된다는 것은 (화면 2)에서 처럼 해당 프로토콜을 세부적으로 사람이 볼 수 있도록 정리할 수 있다는 의미다).
    두번째로는 정말 다양한 플랫폼에서 동작한다는 점이다. 리눅스(레드햇, SuSE, 데비안, PLD, ROCK, SCO, 슬랙웨어), 솔라리스(인텔, 썬), Irix, FreeBSD, OpenBSD, AIX, HP-UX, 맥킨토시 등 다양한 플랫폼에서 동작한다. 아마 어떤 상용 툴도 이렇게 다양한 플랫폼은 지원하지 못할 것이다.
    그리고 마지막 특징으로는 NAI의 스니퍼, 썬의 Snoop, 쇼미티의 Surveyor, AIX의 iptrace, 마이크로소프트의 Network Monitor, HP-UX의 nettl, 와일드패킷의 Etherpeak 등 여러 상용 패킷 분석기에서 캡처한 것들도 볼 수가 있다는 것이다. 이 같은 놀라운 특징들 때문에 이더리얼의 사용을 강력하게 추천하는 것이다.

     


    필자가 이더리얼을 사용하는 법
    스니퍼 사용시 캡처를 할 때 필터(Filter)를 사용하고, 캡처가 끝난 후에 필요에 따라서 캡처된 데이터에서 필터를 사용하는 방법을 사용했었다. 반면 이더리얼의 특징 중 하나가 캡처하는 화면을 실시간으로 보는 것은 기본이고, 필요에 따라서는 캡처하면서도 각종 옵션으로 화면에 나타나는 것들을 조절, 분석하는 데 많은 도움을 줄 수가 있다는 점이다. (화면 3)은 이더리얼에서 캡처를 시작할 때 화면이다.



    (화면 3) 캡처를 시작할 때의 다양한 옵션       


    이더리얼을 비롯한 대부분의 패킷 분석기는 캡처 시작 전에 몇 가지 기본적인 설정을 하고나서 시작한다. 여러분들이 처음 이더리얼을 구동했다면 제일 먼저 어떤 인터페이스에서 구동할 것인가를 결정해야 한다. 많은 사람들이 이 선택을 잘못하고 캡처를 시작하면서 되려 이더리얼이 몹쓸 툴이라고 흉보는 경우가 많다.
    필자는 그동안 많은 시간을 투자해 실시간으로 캡처하면서 보는 습관을 들였다. 이 방법이 분석에 많은 도움이 돼 이제는 'Display Options'에 있는 두가지 옵션(화면 3에서 선택된)을 대부분 기본으로 사용한다. 이 옵션을 사용하면 실시간으로 캡처된 리스트들이 자동으로 업데이트되고, 리스트를 보여주는 스크롤이 자동으로 올라가 최근 패킷만 볼 수가 있다. 때문에 실시간으로 패킷을 보고 싶은 필자에게는 아주 유용하다.
    (화면 3) 중 'Name Resolution'이라는 부분은 화면에 보여지는 MAC, IP, TCP/UDP 포트 등을 연관된 이름들로 확인해 보여주는 옵션이다. 예를 들면 MAC은 앞에 있는 3바이트가 제조사 MAC이기 때문에 이를 검색해 보여주고, IP에 해당하는 컴퓨터 이름(DNS 이름), TCP/UDP에 해당하는 프로토콜 이름(TCP 80 → HTTP)으로 보여준다.
    하지만 이 옵션은 유용하면서도 실시간으로 많은 패킷들을 받아서 보여줘야 하는 프로그램 입장에서는 많은 부하를 줄 수 있다. 그러니 수십 Mbps 이상을 캡처할 때는 가급적 이들 옵션은 사용하지 않는 것이 낫다(하지만 실제로는 여러분이 직접 경험하고 느껴본 후에 어떤 것이 효율적일지 나만의 옵션을 찾는 게 중요하다).
    만약 실시간으로 패킷을 볼 게 아니라 필요에 따라서 캡처했다가 나중에 분석할 계획이라면 파일로 저장하는 옵션을 선택하자. 네트워크에 발생하는 문제들은 이번에 필자가 일본에서 겪었던 것처럼 언제 어느때 발생할지 모르는 경우가 있기 때문에 그것 때문에 실시간으로 패킷을 계속 보고 있는 것이 때로는 바보같은 일이기 때문이다.
    이 경우에는 패킷을 일정한 사이즈, 시간마다 자동으로 저장해 나중에 분석이 필요할 때 어느 파일부터 선택해서 분석해야 하는지에 도움이 될 수 있다. 그렇다고 내 컴퓨터의 하드디스크가 크다고 무조건 수 기가바이트로 남기는 것은 나중에 분석을 안하겠다고 하는 것과 마찬가지다. 더구나 윈도우도 그렇게 큰 크기는 읽지 못할뿐더러 수백 메가바이트나 되는 데이터를 프로그램에서 띄운다는 것은 절대 불가하다.


    캡처 필터/화면 필터 잘 쓰는 사람이 고수
    이더리얼의 고수는 바로 필터를 자유자재로 사용하는 사람이다. 우선 캡처 필터는 리눅스에서 tcpdump를 한번이라도 써 봤던 사람이라면 이해할 만한 문법으로 돼 있다. 처음에는 어려워 보이지만 조금만 이해하면 패킷의 다양한 옵션까지도 선택할 수가 있다.
    캡처 필터는 수동으로 입력하는 게 기본인데, 잘 모르겠다면 (화면 3)에 있는 캡처 필터 부분을 클릭해 보면 기본 예제를 볼 수 있다. 물론 'Help'를 누르면 추가 예제 몇 개가 더 나올 것이다. 여기서 중요한 것은 단지 필터 한 개를 잘 사용하는 것이 아니라는 점이다. and, or, not 등의 다양한 연산을 잘 사용하는 것이 제일 중요하다. 다음은 이더리얼에 있는 예제들이다.


    (1) 08:00:08:15:ca:fe 라는 MAC을 가진 호스트에서 사용하는 패킷만 캡처
    ether host 08:00:08:15:ca:fe


    (2) 192.168.0.10라는 IP 주소에서 오는 패킷이나 그쪽으로 향하는 패킷 캡처
    host 192.168.0.10


    (3) TCP 프로토콜 중 포트 80을 사용하는 패킷 캡처
    tcp port 80


    (4) IP 주소가 192.168.0.10이면서 TCP 포트 80을 사용하지 않는 패킷 캡처
    host 192.168.0.10 and not tcp port 80


    위의 예 뿐만이 아니라 arp, 출발지(src), 목적지(dst), 서브넷 기준으로도 구분이 가능하고 패킷사이즈에 따른 캡처도 가능하다. 이를 가장 잘 익힐 수 있는 방법은 메뉴얼을 읽거나 tcpdump 옵션을 찾아보는 것이다.


    <not 사용을 습관적으로>
    필터를 사용할 때 종종 사용하게 될 옵션 중 하나가 바로 'not'이다. 이 옵션은 말 그대도 어떤 특정 패킷들은 너무도 당연해서 볼 필요가 없을 경우에 이들을 제외한 나머지를 보고자 할 때 사용하는 옵션이다.

     

    다음은 icmp를 이용한 웜인 웰치아(Welchia)가 한창 유행일 때 사용했던 옵션이다. 이 패킷의 특징은 icmp를 사용하는 핑(ping)을 이용해 불특정 네트워크를 검색하느라 라우터, 파이어월과 같은 IP 처리 장비들이 제대로 서비스를 못하는 문제를 야기시켰다. 하지만 이 패킷은‘92바이트'라는 패킷 사이즈를 가지는 게 특징이었으므로 단순히 icmp를 네트워크에서 분리할 경우에는 어떤 호스트에 문제가 있는지를 찾기는 쉽지 않았다. 하지만 툴을 제대로 사용할 줄 아는 사람이라면 다음과 같은 방법을 쓰면 어렵지 않게 구분해 낼 수가 있다.



    (화면 4) 필터 4. icmp and ip[2:2] = 92


    (화면 4)의 옵션인 'icmp and ip[2:2] = 92'를 살펴보자. 무슨 암호문자 같기도 하지만 해석해 보면 그리 어렵지는 않다. 우선 icmp이면서 ip[2:2] = 92라고 했다. ip는 ip인데 뒤에 [2:2]라는 이상한 옵션이 추가됐다. 여기서 앞에 숫자 2는 바이트 단위로, IP 헤더 중에 2바이트인 16비트 뒷부분을 가리킨다. 그리고 뒤에 숫자 2는 거기부터 다시 16비트이다. 바로 그 값이 92인 것을 가리킨다. 물론 한번에 이해하기가 쉽지는 않을 것이다. 사실 필자도 처음 이 옵션을 찾아냈을 때 한참을 해매야만 했다.




    (그림 2)는 IP 헤더의 구조다. 이제 앞에서 설명한 2:2의 비밀을 파헤쳐 보자. (그림 2)에서는 32비트 단위로 구분을 했고 그중에 헤더의 16비트, 즉 2바이트 뒤가 바로 패킷의 길이를 나타내는 부분이다. 그리고 길이를 나타내는 필드는 전체 16비트, 즉 2바이트 안에 표기하게 돼 있다. 그 길이가 92인 값을 찾아달라는 것이다. 이제 이해가 좀 될 것이다. 아직도 이해가 안된다면 지난 3월호에 연재했던 IP 헤더에 대해 다시 한번 공부하자.
    실시간으로 패킷을 볼 때면 리스트가 너무 빨리 지나가기 때문에 사실 이더리얼에 능숙한 사람이라도 보고 싶은 부분만을 잡아내기란 쉽지 않다. 그래서 이더리얼은 실시간 패킷을 볼 때 바로바로 필터를 적용할 수 있는 방법을 제공한다. 이 필터의 장점은 캡처 필터에서 사용된 옵션대로 필터는 하고 있지만, 화면에 보이는 부분만 다시 필터를 사용할 수가 있다는 것이다. 그렇기 때문에 다시 화면 필터를 제거하면 캡처 필터에서 사용했던 필터대로 나타나게 된다. 가장 중요한 것은 실시간 작업을 할 수가 있다는 점이다. 그런데 이 필터 옵션은 캡처 옵션과는 조금 다른 형태를 갖고 있으므로 여러분들이 조금만 신경 쓰면 어렵지 않게 좋은 결과를 얻을 수가 있다.



    (화면 5) ICMP/TCP 등이 혼재돼 있는 캡처


    (화면 5)처럼 몇가지 필터를 사용해 캡처를 시작했는데 캡처 중에 icmp만 보고 싶다면 캡처를 정지할 필요가 없다. 그냥 화면 필터 부분에 (화면 6)과 같이 해주면 된다.



    (화면 6) 실시간 캡처 중 다시 icmp만 화면 필터 적용


    (화면 6)은 icmp만을 다시 보기 위해서 적용한 화면 필터로, 필요한 부분을 봤다면 삭제하거나 'Clear'라고 하면 원래의 필터대로 보이게 하는 옵션이다. 실시간으로 되는 것도 제법인데 이 옵션은 아주 마음에 드는 기능 중 하나다. 이 필터를 제대로 쓰고 싶다면 옆에 있는 Expression 메뉴를 클릭해 보자. 필자가 웰치아(Welchia)를 이더리얼로 캡처할 때 사용한 옵션이 화면 필터에서는 (화면 7)과 같이 Expression 옵션에서 사용할 수가 있다.




    (화면 7) 화면 필터의 Expression



    (화면 8) IP 전체 길이 92바이트 선택


     

    (화면 8)은 Expression의 IP 부분만 선택해서 세부적인 필드 중에 길이(Length) 필드의 값이 92인 것을 선택한 화면이다. 이처럼 여러분들이 프로토콜에 대한 세부지식(헤더구조 등)만 안다면 얼마든지 많은 옵션을 사용해 정말 분석다운 분석을 할 수가 있다.
    이번에는 컬러 필터라는 것을 한번 사용해 보자. 컬러 필터는 화면 필터와 같은 옵션이지만 여기에 색(Color)을 겸비해서 사용하는 방법으로 가독성을 한층 올려주는 방법이다. 메뉴의 'Color Rules'를 선택해 필터와 보여지는 색깔을 선택하면 된다. 배경색깔과 각 패킷 리스트의 글씨를 구분할 수 있다.



    (화면 9) TCP 패킷들의 색깔만 필터 적용


    이 기능은 단순히 화려하게 보이려는 목적보다는 패킷을 조금 더 빨리 분석하기 위한 이더리얼의 배려이니 자신만의 컬러 필터를 만들어 별도로 저장해 보자.


    캡처하는 방법에 대한 고민
    앞에서 필자가 거짓말을 조금 보태서 이더리얼을 이용하면 많은 것을 할 수 있고 마치 이더리얼을 사용할 줄 알면 패킷 분석의 고수가 될 것 같은 인상을 풍긴 듯 한다. 그런데 사실은 정말 그렇다. 
    패킷분석기를 이용해도 별로 소용이 없다라고 말하는 이들 중 대부분은 필자가 아는 한 잘못된 방법이나 위치에서 패킷 분석기를 이용해 캡처를 하는 경우다. 때문에 정작 문제가 발생했을 때 중요한 패킷을 캡처하지 못한 경우가 많다. 사실 이번 출장에서 필자가 문제를 해결할 때도 그런 면이 없지 않았다. 하지만 너무 많은 구간에서의 문제 발생 가능성이 있을 때는 어쩔 수 없기도 하다. 생각해 보라. 패킷 분석을 위해 수십 대의 분석 툴을 설치한다는 것이 쉬운 일인가. 더군다나 캡처는 캡처지만 이후에 이 패킷을 모두 분석해야 하는데, 캡처한 데이터가 수백 기가바이트가 넘어간 시점에서는 정말 암담할 뿐이다. 하지만 조금만 신경쓰면 이 같은 문제들은 조금씩 줄일 수는 있다.


    이더리얼을 어디에 연결할까
    문제가 생긴 네트워크에 그냥 이더리얼이 설치된 컴퓨터를 연결한다고 해서 모든 패킷이 캡처되는 것은 분명 아니다. 그렇기 때문에 어떻게 연결해야 할까를 이제는 고민해야 한다.
    만약 내 컴퓨터만의 문제라면 별 문제없이 내 컴퓨터에서 구동만 하면 되지만 네트워크의 다른 호스트의 문제나 다른 패킷을 분석하려면 모든 패킷이 지나다니는 구간을 선택해야 한다. 가장 좋은 방법이 스위치에서 업링크 포트를 미러링하는 방법이다. 이 방법을 사용할 경우, 스위치에서 간단한 미러링 설정만 하면 된다.




    가끔 어떤 작업자는 미러링 설정을 할 때 특정 패킷(In or Out)만 선택하는 경우가 있는데, 이런 부분을 선택할 때도 신중하게 내가 얻고자 하는 데이터와 관련이 있는가를 고민해야 한다. 특별한 문제가 없다면 In/Out을 모두 선택하는 방법을 추천한다. 참고로 일부 스위치들은 미러링을 받는 포트에 연결된 컴퓨터가 다른 포트와의 통신이 안되는 경우가 있다. 이 경우 패킷 분석기가 설치된 컴퓨터를 원격에서 제어하는 방법도 고민해야 한다. 그래서 가끔 LAN 카드를 하나 더 연결하는 경우도 있다.
    그리고 필요에 따라서는 외부와 연결되는 한 포트만 미러링을 해도 되지만 N:1처럼 여러 포트를 미러링해야 할 필요가 있을 수도 있다. 가급적이면 유연한 생각을 갖고 운영할 수 있도록 한다.
    가끔씩 미러링이 현실적으로 불가능할 경우에는 탭(Tap)이라는 장비를 사용하는 경우가 있다. 이 장비를 연결되는 두 장비 중간에 연결해 필요한 패킷들을 패킷 분석 툴이 설치된 컴퓨터로 보내줄 수가 있다. 이런 장비들은 요즘 한창 사용되고 있으므로 알아둘 필요가 있다.

    탭이나 미러링 장비를 연결할 때 가끔 연결에만 신경을 쓰다가 포트 설정(Speed/Duplex)을 무시하는 경우가 있는데, 이 부분도 신경을 쓰도록 하자. 포트 설정이 잘못될 경우 패킷 분석기와의 통신에서 패킷이 손실될 수가 있기 때문이다. 이 같은 사소한 문제로 거사를 놓칠 수는 없지 않겠는가?
    스위치에서 미러링을 하는 이유는 스위치는 허브와 달리 입력되는 패킷이 모든 포트로 전달되지 않기 때문이다(사실 이것이 바로 스위치의 장점이다). 그래서 모든 패킷을 받지 못하기 때문에 미러링을 사용하는 것이다. 그래서 가끔 역으로 입력되는 모든 패킷을 모든 포트로 전달하는 허브를 이용하기도 하는데 개인적으로는 이 방법은 권장하지 않는다. 대량의 트래픽 처리시 아무래도 허브의 성능 부족이나 충돌(Collision) 등으로 인해 분석에 혼란을 야기 할 수도 있기 때문이다.


    컴퓨터의 시간을 맞추자
    이더리얼을 사용할 때 정말로 신경써야 할 게 한가지가 더 남아 있다. 그것은 바로 컴퓨터의 시간을 맞추는 일이다. 지난호의 시스로그 서버도 그렇지만, 이들이 남기는 시간은 모두 컴퓨터에 맞춰진 시간을 기준으로 한다.
    문제가 다시 발생했을 때 분석시 정말 사소한 시간 문제로 고민하지 않으려면 컴퓨터의 시간을 주변의 장비들과 동일하게 맞춰야 한다. 가장 좋은 방법은 NTP(Network Time Protocol)를 사용하는 방법인데, 대부분 관리가 가능한 장비들은 NTP를 지원하므로 윈도우용 NTP 클라이언트를 인터넷에서 찾아 시간을 동기화하자.
    혹시라도 이런 작업이 귀찮아서 나중에 분석할 때 시간차를 계산해서 하겠다는 생각은 아예 버려라. 잠깐의 귀찮음이 여러분들의 시간과 실력을 한참 퇴보하게 만들것이다. 패킷분석은 때로는 초 단위 보다 더 세분화해서 하는 경우가 있으니 부디 필자의 조언을 듣기 바란다.
    이더리얼은 공개용이지만 정말 추천할 만한 툴이다. 사실 필자도 처음에는 불법(?) 스니퍼를 사용하면서 패킷 분석기를 접해봤지만 지금은 대부분의 경우 이더리얼을 사용한다. 물론 업무용도로 말이다. 매달 쓰는 원고 내용마다 중요하지 않은 것은 없지만, 이번에 소개한 패킷 분석기는 정말로 그 어느것보다도 중요하다는 점을 강조하면서

    Posted by 1010
    반응형

    HTTP WATCH를 많이 사용하시는데 IE7에서 안되서 답답하셨다면

    찰스를 사용해보세요~

    Posted by 1010
    98..Etc/Etc...2008. 8. 12. 13:08
    반응형
    1. GWT란 무엇인가?

    - Java를 이용해 Rich Ajax 애플리케이션을 제작할 수 있는 오픈 소스 프레임워크.
    - JavaScript/HTML 코드를 자동으로 생성해 냄.
    - 모든 종류의 브라우저를 지원함.
    - Pure JavaScript/DHTML at the client side.
    - Pure Java at the server side.

    2. GWT를 써야 하는 이유?

    - No need to learn/use JavaScript language -> Leverage existing Java skills.
    - No need to handle browser incompatibilities -> GWT handles them for you.
    - No need to learn/use DOM APIs -> Use pure Java APIs.
    - No need to handle forward/backward buttons -> GWT handles this for you.
    - No need to build commonly used widgets -> GWT provides most of them.

    3. GWT의 단점

    - Java 1.4 버전까지만 지원.
    - GWT로 자동 생성된 HTML과 JavaScript 코드는 꽤 무거움.
    - Hosted 모드에서는 실행 속도가 느림.
    - 다양한 Widget을 제공하지 않음.
    - 동기식(Synchronization) RPC(Remote Procedure Call)를 지원하지 않음.
    - Modal 대화창 개발에는 적합하지 않음.
    - 클라이언트는 client 폴더의 하위 폴더에만 접근할 수 있음.
    - 쓸만한 무료 UI 디자이너 툴을 제공하지 않음.
    - 한글 처리 문제.

    * 출처
    - http://www.ibm.com/developerworks/kr/library/os-ad-gwt1/index.html
    - http://www.ibm.com/developerworks/kr/library/os-ad-gwt2/index.html
    - http://www.ibm.com/developerworks/kr/library/os-ad-gwt3/index.html
    - http://www.devbg.org/seminars/seminar-GWT-26-september-2007/AJAX-Applications-with-Google-Web-Toolkit-Nakov-v1.0.pdf
    - http://blog.dev.daewoobrenic.co.kr/tc/jcfblog/entry/Google-Web-ToolkiGWT-%EB%8B%A8%EC%A0%90-%EB%B0%8F-%EC%B0%B8%EC%A1%B0-%EB%A7%81%ED%81%AC
    - http://factorystories.springnote.com/pages/1089916
    - http://naucika.tistory.com/15



    출처 : http://chocodonut.tistory.com/248
    Posted by 1010
    98..Etc/Etc...2008. 8. 12. 13:07
    반응형

    Google Web Toolkit, Apache Derby, Eclipse를 사용하여 Ajax 애플리케이션 구현하기, Part 1: 환상적인 프론트엔드 (한글)

    자바 프레임웍에서 Ajax 애플리케이션 구현을 위한 Google Web Toolkit

    developerWorks
    문서 옵션
    수평출력으로 설정

    이 페이지 출력

    이 페이지를 이메일로 보내기

    이 페이지를 이메일로 보내기

    
    제안 및 의견
    피드백

    난이도 : 중급

    Noel Rappin, Senior Software Engineer, Motorola, Inc.

    2007 년 2 월 06 일

    Google Web Toolkit (GWT)은 동적 Java™Script의 생성에 혁신을 가져왔습니다. GWT를 사용하면, 개발자들은 익숙한 자바 기술을 사용하여 사용자 인터페이스(UI)와 이벤트 모델을 디자인하고 대다수의 브라우저에 익숙한 코드를 만드는 일을 하게 됩니다. 이 글을 통해, GWT의 기초를 설명하고, GWT에서 Asynchronous JavaScript + XML (Ajax) 애플리케이션을 만드는 방법과, 자바 언어로 코드를 작성하는 방법을 설명합니다. 또한 온라인에서 피자를 판매하는 Slicr라고 하는 Web 2.0 비즈니스 샘플을 가지고, GWT 애플리케이션을 생성 및 실행하는 방법을 설명합니다.

    GWT를 사용하여, 전통적인 자바 GUI 인터페이스를 구현하는 것 보다 훨씬 더 쉽게, 더욱 풍부한 Ajax 브라우저 클라이언트 인터페이스를 구현할 수 있다. GWT가 매우 놀랍기는 하지만, 전체 웹 애플리케이션을 이것 하나로 만들 수는 없다. 서버에 데이터 스토어가 있어야 하고, 데이터를 자바 객체로 변환하여, GWT가 서버에서 클라이언트로 전달할 수 있도록 하는 프레임웍이 있어야 한다. 본 기술자료 시리즈에서, 100% 순수 자바 데이터베이스인 Apache Derby를 사용하는 방법을 설명한다.

    이 글에서는 GWT에 대한 중점적인 설명과 더불어, GWT를 설정하는 방법과 사용자 액션에 반응하는 간단한 클라이언트 인터페이스를 구현하는 방법을 설명한다. 후속 기술자료에서는 Derby 데이터베이스를 설치하고, GWT 프론트엔드를 Derby 기반 백엔드에 연결하는 방법을 설명한다. 마지막으로, 개발 환경 밖에서 시스템을 전개하는 방법도 배운다.

    Ajax 리소스 센터에서는 Ajax 프로그래밍 모델과 관련한 기술자료, 튜토리얼, 포럼, 블로그, wiki, 이벤트, 뉴스 등이 제공된다.

    Google Web Toolkit이란 무엇인가?

    GWT를 사용하여, 자바 프로그래밍 언어로 Ajax 애플리케이션을 개발할 수 있다. Ajax 애플리케이션의 장점은 전통적인 UI 애플리케이션과 연결되는 풍부한 인터랙티브 환경일 것이다. 그림 1은 샘플 GWT 인터페이스로서 데스크탑 이메일 애플리케이션이다. 이 데모는 GWT 웹 사이트에서 볼 수 있다.


    그림 1. GWT 이메일 데모
    A GWT e-mail demonstration

    GWT의 가장 고유한 기능은 Ajax 애플리케이션을 만들 수 있고, 자바 언어로 코드를 작성할 수 있다는 점이다. 선호하는 자바 통합 개발 환경(IDE)을 사용하여, 클라이언트를 디버깅 할 수도 있다. 자바 객체를 사용하여 클라이언트와 서버간 통신도 가능하고, 모든 것이 자바 애플릿 보다 가볍다.

    GWT는 기본적으로 컴파일러이다. GWT는 자바 코드를 HTML 페이지에 삽입되어 실행될 수 있는 JavaScript 코드로 변환하여 클라이언트 측 애플리케이션을 실행하는데 사용된다. JavaScript 코드는 거의 모든 브라우저에서 지원되므로 여러분은 프로그램의 인터페이스와 인터랙션에 집중할 수 있다.

    물론, GWT가 컴파일러일 뿐이라면, 거론하지 않았을 것이다. 다행히도, 그 이상의 기능을 한다. GWT는 컴파일러 메커니즘 뿐 만 아니라, 아래와 같은 부가적인 특징이 있다.

    • 표준 UI 위젯은 유연하고, 거의 모든 주요 브라우저에서 실행된다. (Safari와 Opera 포함)
    • 클라이언트 측의 이벤트를 포착하고 응답하는 이벤트 메커니즘.
    • 웹 애플리케이션과 서버 간 비동기식 호출을 관리하는 프레임웍.
    • Ajax 애플리케이션이 예상 Back 버튼 작동에 관여하지 않도록 하는, STATEFUL 브라우저 히스토리를 만드는 메커니즘.
    • JUnit을 사용하여 클라이언트 애플리케이션용 단위 테스트를 작성하는 테스트 프레임웍.

    본 시리즈에서는 이러한 기능들을 설명할 것이다. 하지만 먼저, GWT를 다운로드 및 설치해야 한다.

    GWT 설치하기

    이 글을 쓰고 있는 현재, GWT 최신 버전은 1.2이다. (참고자료) GWT는 Microsoft® Windows® XP, Windows 2000, Linux® 그리고, Mac OS X에서 실행되는 GTK+ 2.2.1 또는 그 이후 버전에서 완벽히 지원된다.

    다운로드 한 압축 파일을 원하는 디렉토리에 압축을 푼다. 버전들마다 약간 다르지만, 기본 엘리먼트는 다음과 같다.

    • 세 개의 .jar 파일: gwt-user.jar 에는 프로젝트 classpath에 필요한 사용자 클래스들이 포함되어 있다. gwt-dev-windows.jar 또는 gwt-dev-linux.jar에는 컴파일러 코드가 포함되어 있다. 세 번째 파일, gwt-servlet.jar는 전개(deploy)에 사용된다.
    • 세 개의 명령행 유틸리티: applicationCreator, junitCreator, projectCreator. (Windows에서는, .cmd가 붙는다.)
    • 샘플 코드 디렉토리

    GWT를 사용할 때, 일부 파일들은 임시 파일들을 관리하기 위해 GWT 홈 디렉토리에 놓인다.

    프로젝트 만들기

    다운로드를 마치면, 가장 먼저 프로젝트를 만들어야 한다. 온라인에서 피자를 파는, Slicr라고 하는 새로운 Web 2.0 비즈니스의 온라인 사이트를 구축할 것이다. GWT 프로젝트를 설정하는 방법들은 IDE를 사용할 것인지의 여부에 따라 다르다. 여기에서는 GWT 명령행 유틸리티의 지원도 받는 무료 Eclipse를 사용하도록 하자.

    명령행 유틸리티를 사용하여 Eclipse 프로젝트를 만든다.

    1. 하드 드라이브의 적당한 곳에 slicr라고 하는 새로운 디렉토리를 만든다.
    2. 새로운 slicr 디렉토리에 명령어 프롬프트를 연다.
    3. 다음 명령어를 입력한다. (슬래시와 그 외 것들을 각자 운영 체계에 맞게 변환한다.)

    <GWT_HOME>/projectCreator -eclipse slicr

    이 명령어로는 GWT 프로젝트에 필요한 최소한의 것만 만든다. 새로운 src 하위 디렉토리와 새로운 .project와 .classpath 파일이 생겼다.

    지금 바로 프로젝트로 시작할 수 있지만, GWT는 그 이상의 구조를 기대한다. 다음 명령어를 사용하여 설정할 수 있다.


    <GWT_HOME>/applicationCreator -eclipse slicr com.ibm.examples.client.Slicr

    -eclipse slicr 인자는 Eclipse 프로젝트의 이름이고, projectCreator에서 사용했던 것과 같아야 한다. (그렇지 않으면, Eclipse 내에서 GWT 애플리케이션을 시작하는데 문제가 생긴다.) 마지막 인자는 애플리케이션의 메인 클래스가 될 클래스 풀네임(fullname)이다. 이때, 패키지명의 마지막은 의미상 client로 정하기로 하고, client 이전의 부모 패키지 이름은 여러분이 알아서 작성하도록 한다. (일단, 'com.ibm.examples'로 부모 패키지 이름을 정하였다.)

    이 명령어는 몇 가지 파일을 생성한다. .java 파일은 메인 클래스이고, 해당 클래스에 동반된 패키지 명에 부합되게 부모 디렉토리 들이 생성된다. 여러분은 최하위 디렉토리에는 clientpublic이 생성되어 있는 것을 확인 할 수 있다. 앞에서 거론한 public 디렉토리에는 Slicr.html와 Slicr.gwt.xml을 확인 할 수 있다. 또한, GWT는 eclipse가 사용하게 될 Slicr.launch파일과 두 개의 셀 스크립트 파일을 생성 한다.




    위로


    Eclipse로 옮기기

    이제 프로젝트를 Eclipse로 옮긴다.

    1. Eclipse를 열고, File > Import를 클릭한다.
    2. 새로운 창에서, General 트리를 확장하여 Existing Projects into Workspace를 선택한다.
    3. Next를 클릭한 다음 Browse를 클릭하여 slicr 루트 디렉토리를 선택한다.
    4. 프로젝트를 선택하고, Copy projects into workspace 옵션이 설정되지 않도록 한다. (프로젝트를 옮기지 않을 것이기 때문이다.)

    이 프로세스를 통해, 코드를 Eclipse로 옮겨서 볼 수 있다. GWT는 세 개의 중요한 파일들을 만들었다. 첫 번째가 com.ibm.examples 패키지에 있는 slicr.gwt.xml 파일이다. Listing 1은 XML 설정 파일이다.


    Listing 1. slicr.gwt.xml
                    
    <module>
        <inherits name='com.google.gwt.user.User'/>
        <entry-point class='com.ibm.examples.client.Slicr'/>
    </module>
    

    여기에서 GWT 애플리케이션에 대한 모듈을 정의할 수 있다. 모듈(module)은 GWT 코드의 기본 단위이고, 클라이언트가 사용하는 HTML 페이지가 참조한다.

    두 번째 파일은 퍼블릭 디렉토리에서 만들어진 Slicr.html 파일이다. 이것은 웹 애플리케이션의 프론트 페이지로서 클라이언트로 보내지는 .html 파일이다. 실제로 필요하지 않는 많은 코멘트들이 들어있다. Listing 2는 파일의 핵심 부분이다.


    Listing 2. Slicr.html
                    
    <html>
        <head>
            <title>Wrapper HTML for Slicr</title>
            <meta name='gwt:module' 
                content='com.ibm.examples.Slicr'>
        </head>
        <body>
            <script language="javascript" 
                src="gwt.js"></script>
            <iframe id="__gwt_historyFrame" 
                style="width:0;height:0;border:0"></iframe>
            <h1>Slicr</h1>
            <p>
                This is an example of a host page for 
                the Slicr application. 
            </p>
            <table align=center>
                <tr>
                    <td id="slot1"></td>
                    <td id="slot2"></td>
                </tr>
            </table>
        </body>
    </html>
    

    가장 중요한 것은 바로 위의 .html 파일이다. 원하는 어떤 HTML이라도 추가할 수 있다. 가장 중요한 것은 HTML 파일에 원하는 어떤 내용이라도 추가 및 변형 할 수 있다는 것이다. 단지, GWT는 아래 네 가지 엘리먼트에 의해서 실행된다.

    • meta 태그: name 애트리뷰트와 content 애트리뷰트는 모듈의 완전한 형식을 갖춘 논리적 이름이다. (XML 모듈 파일과, 확장자 없는 XML의 파일 이름을 포함하고 있는 패키지이다.) 이 태그는 HTML 페이지를 특정 모듈로 연결시킨다. 페이지를 호출하면 모듈이 시작된다. (특히, 모듈에 있는 모든 엔트리 포인트들이 초기화 된다.)
    • script 태그: 이 태그는, GWT 자바 코드를 JavaScript 코드로 변환할 때 생성된 파일 중 하나인 gwt.js라고 하는 파일을 로딩한다. 이 파일은 모든 클라이언트 코드의 로딩을 제어하기 때문에, 프로그램을 실행하는 것 보다 이를 포함시키는 것이 더 중요하다.
    • iframe 태그: 이 태그를 쓰여진 대로 정확히 사용하면 웹 애플리케이션은 히스토리와 상태를 기억할 수 있다. GWT 애플리케이션에서 Back 버튼을 실행 불가로 만들 필요가 없다.
    • td 태그: 특정 .html 파일에 있는 이 태그에는 JavaScript 식별자들이 포함된다. 특별한 것은 없지만, GWT는 이 식별자를 엘리먼트를 배치할 장소로서 사용한다.

    자바 시작 클래스

    GWT는 재사용이 가능한(boilerplate) 자바 시작 클래스도 Listing 3과 같이 만든다.


    Listing 3. 자바 시작 클래스
                    
    public class Slicr implements EntryPoint {
    
      public void onModuleLoad() {
        final Button button = new Button("Click me");
        final Label label = new Label();
        button.addClickListener(new ClickListener() {
          public void onClick(Widget sender) {
            if (label.getText().equals(""))
              label.setText("Hello World!");
            else
              label.setText("");
          }
        });
        RootPanel.get("slot1").add(button);
        RootPanel.get("slot2").add(label);
      }
    }
    

    위의 자바 클래스는 client 패키지에 있고, 이로 인하여 GWT는 해당 코드를 JavaScript로 컴파일 할 것이다. 이것은 파일에 해당 특정 제한이 있는 것 같지만, 코드 내부를 보면 기본적인 Java 1.4 코드일 뿐이다.EntryPoint 인터페이스는 onModuleLoad()만을 정의하였으며, GWT는 이 모듈을 참조하는 HTML 페이지가 로딩되면 이 메소드를 자동으로 호출하게 되는 것이다.

    위의 코드는 단순하다고 할 수 있다. 처음 두 라인에 버튼과 레이블을 정의한다. 그 다음 두 라인에서는, RootPanel.get() 메소드를 사용하여, 이 위젯들을 HTML 페이지의 특정 엘리먼트로 연결시킨다. 이 메소드에 대한 인자는 HTML 페이지에 정의된 엘리먼트의 JavaScript ID이다.

    위의 코드 라인들 사이에 Swing에서와 같이 이벤트를 묶는데 사용하는 것과 비슷한 방식을 사용하여 이벤트 리스너(eventListner)를 정의한다. 위의 경우, 버튼을 클릭하면 ClickListener가 호출된다.




    위로


    GWT 프로그램 실행하기

    GWT가 만든 샘플 프로그램을 실행해 보자. GWT 프로그램을 실행하는 두 가지 방법이 있다. 바로 웹 모드(Web mode)와 호스트 모드(hosted mode)이다. 웹 모드는 완전한 전개 모드로서, GWT 프로그램을 JavaScript 코드로 컴파일 한 후에 실행한다.

    호스트 모드는 개발하는 동안 사용된다. 호스트 모드란, 클라이언트와 서버 코드를 한번에 시뮬레이트 하면서, 개발하는 동안 전개를 단순화 시키는 일종의 에뮬레이터이다. (호스트 모드는 Mac OS X에서는 불가능하다.) 디버거와 함께 IDE를 사용한다면, 호스트 모드에서 GWT 프로그램을 실행할 수 있으며, JavaScript로 컴파일 될 코드 부분에서 중단점(breakpoint)을 지정하고 그의 변수(variable)의 변경사항을 알아낼 수 있다. 이러한 부분은 개발 시 매우 유용하며, GWT로 작업하게 되면, 대부분 호스트 모드를 사용하게 된다.

    또한, 여러 가지 방법들로 호스트 모드를 실행할 수 있다. 이전에 실행했던 applicationCreator 스크립트는 명령행에서 호출할 수 있는 Slicr-shell 스크립트를 만들어서 호스트 모드를 실행할 수 있도록 하였다. 또한, 이 글 초반에, GWT 프로젝트를 Eclipse로 반입하는 방법을 설명했다. Eclipse 프로젝트에서, Run 메뉴 또는 툴바에서 Debug 또는 Run을 선택할 수 있다. 생성된 창을 통해서는 Java Application을 클릭하여 Slicr 옵션을 볼 수 있다. 이 옵션은 GWT가 만들었고, 나머지 프로젝트와 함께 Eclipse로 반입했던 Slicr.launch 파일에서 사용할 수 있다.


    그림 2. 호스트 모드 호출하기
    Invoking hosted mode

    호스트 모드에서 실행할 때, 두 개의 창이 나타난다. (처음 실행할 경우, 호스트 모드 설정이 초기화되는 동안 1분여의 시간이 걸린다.) 그림 3에서 보이는 첫 번째 창의 이름은 Google Web Toolkit Development Shell / Port 8888.이다. 여기에는 GWT의 에러와 로그 메시지들이 들어있다. 툴바를 사용하여, 새로운 호스트 브라우저를 열 수 있고, 스크린상의 로그를 확장, 축소, 삭제할 수 있다.


    그림 3. 호스트 모드 쉘 윈도우
    Hosted mode shell window

    그림 4에 보이는 두 번째 창은 브라우저 모습이다. 보다시피, slicr.html 페이지에서 생성된 정적 HTML과 Slicr.java EntryPoint 클래스에서 생성된 버튼 위젯이 있다. 버튼을 클릭하면 레이블이 선택된다. 설정 단계에서 오류가 있었다면, 이 창을 볼 수 없고 대신 쉘에 에러 메시지가 나타난다. 모든 이름들이 정확한지를 확인하라. (특히, .launch 파일에서, 정확한 프로젝트 디렉토리가 지정되었는지를 확인한다.)


    그림 4. 호스트 모드 브라우저
    Hosted mode simulated browser

    클라이언트 설정하기

    이 글에서는 스크린상에서 위젯을 실행시키는 것에 초점을 맞추겠다. 그림 5에 보이는 스크린은 매우 단순해 보이지만, 꽤나 기능적이다.


    그림 5. Slicr
    Slicr

    페이지가 로딩될 때 이러한 위젯들을 생성시키려면, EntryPoint 클래스의 onModuleLoad() 메소드에 코드를 넣어야 한다. Listing 4는 두개의 데이터를 정의하고, 각 패널을 구현하기 위해 헬퍼를 호출하는 메소드를 정의하고 있다. 이 코드가 실행되려면, slicr ID로 HTML 페이지에 엘리먼트를 삽입해야 한다. 이렇게 하면 리스팅의 RootPanel.get() 명령어가 그 페이지의 엘리먼트를 찾을 수 있다. 가장 쉬운 방법은 이전 HTML 리스팅의 테이블을 >div id="slicr" /<로 대체하는 것이다.


    Listing 4. 모듈 로드 이벤트 핸들러
                    
    private DockPanel panel;
    private List clearables;
    
    public void onModuleLoad() {
        clearables = new ArrayList();
        initDockPanel();
        panel.add(buildActionPanel(), DockPanel.SOUTH);
        panel.add(buildPizzaTypePanel(), DockPanel.WEST);
        panel.add(buildToppingPanel(), DockPanel.EAST);
        RootPanel.get("slicr").add(panel);
    }
    

    위젯 설정하기

    DockPanel에 모든 위젯을 설정하도록 한다. GWT에서의 DockPanel이란, Swing에서 BorderLayout을 사용하는 Panel에 해당한다. Swing에서는 한 개의 panel 클래스와 여러 개의 레이아웃 매니저가 있는 반면, GWT에는 여러 Panel 서브클래스가 있고, 각각 자식 위젯을 전개하는 고유의 알고리즘이 있다. 다른 패널 클래스로는 SimplePanel, HTMLTable, FlowPanel, StackPanel 등이 있다. DockPanel을 만드는 것은 어렵지 않다. 아래 Listing 5에서와 같이 세터(setter)가 이 일을 대신한다.


    Listing 5. 메인 패널 초기화 하기
                    
    private void initDockPanel() {
        panel = new DockPanel();
        panel.setBorderWidth(1);
        panel.setSpacing(5);
    }
    

    SOUTH 패널(button) 생성하기

    DockPanel의 선착순 원리를 따르기 때문에, 먼저 하단부 패널을 정의한다. 이런 방식으로, SOUTH 위젯은 전체 패널에서 실행한다. 아래 Listing 6과 같이 HorizontalPanel을 액션 패널로서 정의한다. (GWT의 HorizontalPanel이란, Swing에서의 box와 비슷하다고 보면 된다.)


    Listing 6. SOUTH (buttons) 패널
                    
    public HorizontalPanel buildActionPanel() {
        HorizontalPanel actions = new HorizontalPanel();
        actions.setSpacing(10);
        Button clear = new Button("Clear");
        clear.addClickListener(new ClearClickListener());
        Button newPizza = new Button("Another Pizza");
        Button submitOrder = new Button("Submit");
        actions.add(clear);
        actions.add(newPizza);
        actions.add(submitOrder);
        return actions;
    }
    

    GWT Button 위젯을 사용하여 세 개의 버튼을 만든 다음, 패널에 추가한다. 또한, 나중에 정의하게 될 Clear 버튼에 ClickListener를 만든다. GWT는 이벤트 리스너들을 Swing과는 다르게 나눈다. ClickListener는 마우스 클릭만 리스닝한다. (종종, 인라인 클래스로서 정의된 리스너를 보곤 하는데, 이 스타일은 읽고 테스트 하기 까다롭기 때문에 네임드 클래스를 만들었다.)

    WEST 패널(pizza type) 생성하기

    pizza type 의 패널은 Listing 7과 같이 복잡하지 않다. GWT RadioButton 위젯을 사용하면 된다.


    Listing 7. WEST (pizza types) 패널
                    
    public static final String[] PIZZA_TYPES = new String[] {
        "Thin Crust Medium", "Thin Crust Large", 
        "Thin Crust X-Large", "Thick Crust Medium", 
        "Thick Crust Large"
    };
    
    private VerticalPanel buildPizzaTypePanel() {
        VerticalPanel pizzaTypes = new VerticalPanel();
        HTML label = new HTML("<h2>Pizza</h2>");
        pizzaTypes.add(label);
        for (int i = 0; i < PIZZA_TYPES.length; i++) {
            RadioButton radio = new RadioButton("pizzaGroup", 
                PIZZA_TYPES[i]);
            clearables.add(radio);
            pizzaTypes.add(radio);
        }
        return pizzaTypes;
    }
    

    HTML을 실행하는 레이블인 HTML 위젯을 사용할 것이다. HTML 위젯은 결국 HTML상의 <span> 태그 주위의 래퍼(Wrapper)로서 역할을 한다. RadioButton 컨스트럭터(constructor)는 두 개의 인자들을 취한다. 첫 번째는 라디오 버튼용 스트링 레이블이고, 두 번째는 텍스트 레이블이다. 각 버튼을 패널과 인스턴스 리스트에 추가하면 이 리스너들 중 한 곳에서 사용하게 될 것이다.


    EAST 패널 (toppings) 만들기

    topping 패널은 Listing 8처럼 좀더 복잡하다. 사용자가 다양한 토핑을 가진 피자를 만들 수 있도록 해야 한다. 토핑 버튼을 클릭하면 두 쪽 모두 체크되지만, 한 쪽만 체크되거나, 개별적으로 삭제될 수 있다. 모든 것의 줄을 맞춰야 하기 때문에 Grid를 사용한다.


    Listing 8. 토핑 그리드
                    
    public static final String[] TOPPINGS = new String[] {
        "Anchovy", "Gardineria", "Garlic", 
        "Green Pepper", "Mushrooms", "Olives", 
        "Onions", "Pepperoni", "Pineapple", 
        "Sausage", "Spinach"
    };
    
    private VerticalPanel buildToppingPanel() {
        VerticalPanel toppings = new VerticalPanel();
        toppings.add(new HTML("<h2>Toppings</h2>"));
        Grid topGrid = new Grid(TOPPINGS.length + 1, 3);
        topGrid.setText(0, 0, "Topping");
        topGrid.setText(0, 1, "Left");
        topGrid.setText(0, 2, "Right");
        for (int i = 0; i < TOPPINGS.length; i++) {
            Button button = new Button(TOPPINGS[i]);
            CheckBox leftCheckBox = new CheckBox();
            CheckBox rightCheckBox = new CheckBox();
            clearables.add(leftCheckBox);
            clearables.add(rightCheckBox);
            button.addClickListener(new ToppingButtonListener(
                    leftCheckBox, rightCheckBox));
            topGrid.setWidget(i + 1, 0, button);	
            topGrid.setWidget(i + 1, 1, leftCheckBox);
            topGrid.setWidget(i + 1, 2, rightCheckBox);
        }
        toppings.add(topGrid);
        return toppings;
    }
    

    VerticalPanelHTML 위젯을 사용한다. GWT Grid에 모든 것을 놓기 때문에, 그리드의 크기를 설정해야 한다. 그리드의 각 셀에는 플레인 텍스트나, 또 다른 GWT 위젯을 포함시킬 수 있다. 각 행(row)에, 버튼과 두 개의 체크 박스를 만들고, 이들을 셀에 맞춰 정렬한다. 버튼용 리스너를 추가하고, 체크 박스를 clearable 리스트에 놓는다.

    리스너 정의

    위젯을 설정했다면, 두 개의 정의된 리스너를 보자. 두 개 중 더 단순한 것이 Clear 버튼에 대한 것이다. Listing 9와 같이 이 버튼은 clearable 리스트에서 실행되고, 모든 것을 지운다.


    Listing 9. Clear 버튼에 정의된 리스너
                    
    private class ClearClickListener implements ClickListener {
        public void onClick(Widget sender) {
        for (Iterator iter = clearables.iterator(); iter.hasNext();) {
                CheckBox cb = (CheckBox) iter.next();
                cb.setChecked(false);
            }
        }
    }
    

    주: GWT에서, RadioButton은 실제로 CheckBox의 서브클래스이다. 따라서 위 코드는 class cast exception을 트리거(trigger)하지 않는다.

    토핑 버튼용 리스너는 더 복잡하다. 제휴 체크 박스들 중 어떤 것도 선택되지 않으면, 이 리스너는 두 개의 체크 박스 모두를 선택한다. 다른 상황에서는, 두 가지 모두를 지운다. Listing 10은 그 예이다.


    Listing 10. 버튼에 정의된 리스너
                    
    private class ToppingButtonListener implements ClickListener {
    
        private CheckBox cb1;
        private CheckBox cb2;
    
        public ToppingButtonListener(CheckBox cb1, CheckBox cb2) {
            this.cb1 = cb1;
            this.cb2 = cb2;
        }
    
        public void onClick(Widget sender) {
            boolean unchecked = !cb1.isChecked() && !cb2.isChecked();
            cb1.setChecked(unchecked);
            cb2.setChecked(unchecked);
        }
    }
    

    공유 사이트...

    digg Digg
    del.icio.us del.icio.us
    Slashdot Slashdot

    예고

    이번 글에서는 slicr 클라이언트를 구현해 보았다. 다음 글에서는 Derby 데이터베이스를 사용하여 서버 측에서 데이터 레이어를 구현하고, 데이터베이스에서 온 데이터를 GWT 클라이언트로 보내질 수 있는 자바 객체들로 변환하는 방법을 설명하겠다. 서버와 클라이언트를 연결하는 원격 프로시저 아키텍처도 설명한다.

    서버 측이 개별적으로 실행되어야 한다면, 개발 및 실행 환경에 이를 전개하는 방법을 고려해야 한다. 또한, 인터페이스를 보기 좋은 모양으로 만드는 방법도 배울 것이다. 그 전에 GWT 다운로드 사이트에 가서 직접 실행해 보기 바란다.

    기사의 원문보기



    참고자료

    교육

    제품 및 기술 얻기

    토론


    필자소개

    Noel Rappin 박사는 Georgia Institute of Technology의 Graphics, Visualization, and Usability Center 소속이며, Motorola의 소프트웨어 엔지니어이다. wxPython in Action and Jython Essentials를 공동 집필했다.

    Posted by 1010
    01.JAVA/Java2008. 8. 12. 12:09
    반응형

    개발자가 놓치기 쉬운 자바의 기본원리

    • 전성호(커뮤니티본부 커뮤니티개발1팀), 2006년 10월

    초록(abstract)

    개발자가 놓치기 쉬운 자바의 기본 원리에 대하여 기본적이긴 하지만 개발하면서 느끼고 경험한 내용을 정리하였다.

    목차

    1 객체지향의 구멍 static
    1.1 Java는 객체지향 언어이다?
    1.2 전역변수
    2 Java는 Pointer언어이다? (Java에는 Pointer밖에 없다?)
    2.1 Java는 primitive형을 제외하곤 모두 Pointer이다
    2.2 null은 객체인가?
    2.3 String에 대하여
    2.4 객체지향의 캡슐화 파괴 주의
    2.5 배열에 대하여
    2.5.1 배열은 object 인가?
    2.5.2 배열의 length는 왜 field(member variable)인가?
    2.5.3 final과 배열에 대하여...
    2.5.4 "Java에서의 다차원 배열은 존재하지 않는다."
    2.6 인수(parameter/argument)전달의 개념
    2.6.1 "Java에서 parameter(argument) 전달은 무조건 'call by value' 이다"
    2.6.2 "C와 같은 언어는 static linking이지만, Java는 dynamic linking이다."
    2.7 GC 에 대하여 잠깐!
    2.7.1 "Garbage Collection은 만능이 아니다."
    2.8 Java Pointer 결론
    2.8.1 "결국 Java에는 pointer가 있는 것인가, 없는 것인가?"
    3 상속과 interface의 문제점
    3.1 상속
    3.1.1 상속에 있어서의 생성자(constructor)
    3.1.2 "down cast는 본질적으로 매우 위험하다"
    3.1.3 "추상클래스에 final이 있으면 compile error이다"
    3.2 interface
    3.2.1 "interface는 interface일뿐 다중 상속의 대용품이 아니다."
    3.3 상속 제대로 사용하기
    4 package와 access 제어에 관한 이해
    4.1 package
    4.1.1 "package는 '계층구조' 인가?"
    4.1.2 "compiler 가 인식하는 class검색 순서(소스코드내 클래스가 발견될 경우 그 클래스의 위치를 찾는 순서)"
    4.2 access 제어
    4.2.1 "interfacde member의 access 제어"
    4.2.2 그렇다면 interface를 다른 package에 대하여 숨기고 싶으면 어떻게 하는가?
    5 기타 Java 기능
    5.1 Thread
    5.1.1 "Multi Thread에서는 흐름은 복수이지만 data는 공유될 수 있다."
    5.1.2 "Thread는 객체와 직교하는 개념이다."
    5.1.3 "Synchronized 의 이해"
    5.1.4 "Thread 사용법의 정석은?"
    5.2 Exception
    5.2.1 "finally 절은 반드시 어떠한 경우에도 실행되는가?"
    5.2.2 "예외의 종류 3가지 (Error, RuntimeException, 그밖의 Exception)"
    5.2.2.1 Error
    5.2.2.2 RuntimeException
    5.2.2.3 그밖의 Exception
    5.2.3 "OutOfMemoryError는 어떻게 처리해야 하는가?"
    5.3 Object Serialize
    5.3.1 "Serialize를 위해서는 marker interface인 java.io.Serializable interface를 implements해야한다."
    5.3.2 "super class는 Serializable이 아닌데 sub class만 Serializable인 경우의 문제점"
    5.3.3 "transient field의 복원(?)관련"
    5.3.4 "Stack Overflow에 주의하라!"
    5.4 "nested class / inner class / 중첩클래스"
    5.4.1 "중첩클래스의 개념"
    5.4.2 "내부클래스는 부모의 참조를 몰래 보유하고 있다."
    5.4.3 "local inner class에 대하여"
    5.4.4 "anonymous class(무명클래스)에 대하여"
    6 이래도 Java가 간단한가?
    6.1 method overload 에서의 혼란?
    6.1.1 "overload란 이름이 가고 인수가 다른 method에 compiler가 다른 이름을 붙이는 기능"
    6.1.2 "그렇다면 overload에서 실제로 혼동되는 부분은 무엇인가?"
    6.1.3 (참고) 또다른 혼동, overload한 method를 override 하면?
    6.2 상속/override/은폐 에서의 복잡함
    6.2.1 "Java class의 member 4 종류"
    6.2.2 "override시 method 이름에 대한 함정"
    6.2.3 "또다른 나의(?) 실수 - 말도 안되는 오타"
    6.2.4 "static member를 instance를 경유하여 참조해서는 안 된다."
    6.2.5 "super keyword는 부모의 this"
    6.3 상속에 관한 또 다른 문제
    6.4 그밖의 함정
    6.4.1 "생성자에 void 를 붙인다면?"
    6.4.2 "if / switch 의 함정"
    7 Java 기능 적용 몇가지
    7.1 대규모 개발에서 interface 분리하기
    7.1.1 "interface 분리의 필요성"
    7.2 Java에서의 열거형
    7.3 Debug write
    8 Java 5.0 Tiger 에 대하여
    8.1 Working with java.util.Arrays
    8.2 Using java.util.Queue interface
    8.3 java.lang.StringBuilder 사용하기
    8.4 Using Type-Safe Lists
    8.5 Writing Generic Types
    8.6 새로운 static final enum
    8.7 Using java.util.EnumMap
    8.8 Using java.util.EnumSet
    8.9 Convert Primitives to Wrapper Types
    8.10 Method Overload resolution in AutoBoxing
    8.11 가변적인 argument 개수 ...
    8.12 The Three Standard Annotation
    8.13 Creating Custom Annotation Types
    9 The for/in Statement
    9.1 for/in 의 자주 사용되는 형태
    10 Static Import
    10.1 static member/method import
    11 References

    1 객체지향의 구멍 static #

    1.1 Java는 객체지향 언어이다? #

    "Java는 완전한 객체지향 언어이다" 라는 주장을 자주 접하게 된다. 만일 이것이 사실이라면 Java를 사용하는 한 "기존의 절차지향 프로그래밍을 전혀 할수 없을것 같지만 그렇지 않다. 빠져나갈 구멍이 있는 것이다. static을 이용하면 비 객체지향 언어처럼 코딩할 수 있다.

    static method는 instance가 아닌 클래스에 속하는 method로, class method라고 부른다. 반대로 static이 아닌 method는 instance method라고 부른다.

    static method는 this가 없다. instance method에는 숨겨진 파라미터로 this가 건네진다. (아래 "객체지향에 흔희 있는 오해" 참고) 하지만 static method는 절차지향의 함수와 동일하므로 숨겨진 파라미터 this는 없다. 그래서 static method에서는 전달한 this가 없으므로 instance method를 호출하거나 instance field를 참조할 수 없는 것이다.

    (참고) 객체지향에 흔히 있는 오해

    • 오해1. "객체지향에서는 객체끼리 서로 메세지를 주고 받으며 동작한다." 라는 말을 듣고 다음과 같이 생각할 수 있다. "객체지향에서는 객체가 각각 독립하여 움직인다는 것인가, 그러면 각 객체에 독립된 thread가 할당되어 있단 말인가?" 그렇지 않다. "메세지를 보낸다"라는 것은 단순히 각 객체의 함수 호출에 불과하다.

    • 오해2. "객체지향에서는 method가 class에 부속되어 있다"는 말을 듣고 다음과 같이 생각할 수 있다. "그러면 instance별로 method의 실행코드가 복제되고 있는 것이 아닌가?" 물론 이것도 오해다. method의 실행코드는 종래의 함수와 동일한 어딘가 다른곳(JVM의 class area)에 존재하며 그 첫번째 파라미터로 객체의 포인터 this가 건네질 뿐이다.

    • 오해3. "그렇다면 각 instance가 method의 실행코드를 통째로 갖고 있지 않는 것은 확실하지만, method의 실행 코드의 포인터는 각 instance별로 보관하고 있는것이 아닌가?" 이것은 약가 애매한 오해이긴 하다. JVM 스펙에서는 class영역에 실행코드를 갖고 있으며, method 호출시 별도의 stack frame이 생성되어 실행되고 실행 완료시 복귀 주소를 전달한다.

    1.2 전역변수 #

    static에서 public field는 전역변수(global variable, 글로벌 변수)이다. 여기서 "글로벌 변수는 왜 안 되는가"에 대해 잠깐 생각해 본다. 우리는 흔히 "글로벌 변수는 될수있는한 사용하지 않는 것이 좋다"라고 한다. 그 이유는 글로벌 변수는 어디서든 참조할 수 있고 값을 변경할 수 있기 때문이다.

    또한 파라미터나 리턴값으로 교환해야 할 정보를 글로별 변수를 경유(사용)하여 건네주면 함수의 역할이 불분명 해지고 흐름도 애매해 진다. 마지막 이유로는 "글로벌 변수는 하나밖에 없다"는 것이다. 이는 어디서 이값을 변경했는지 알 수 없게 하는 지름길이고 실무에서도 간혹 발생하긴 하지만, 이 하나밖에 없는 변수가 버전업으로 두개가 필요하게 되었을때 확장도 대형 프로젝트에서는 힘들어진다.

    따라서 static에서 public은 final을 붙여 상수로 사용해야지 그 외의 용도는 자제하는 것이 좋을 것이다.

    • (참고) final 초기화에서의 주의점. 예를 들어 다음과 같은 코드를 보았을때 우려되는 점은 무엇인가?

    public final static Color WHITE = new Color(255, 255, 255);

    위의 코드는 java.awt.Color에서 발췌한 것인데, final 변수는 한번 초기화 되면 변경이 불가능한데 object로 초기화 할 경우 WHITE라는 필드가 변경될 수 없는 것이지 그것이 가리키는 객체는 아니라는 점이다.

    과거 신규 서비스 개발시 final 변수 필드에 설정파일을 읽어 cache하는 singleton class의 특정 member 를 이용하여 초기화 할 경우 이 멤버값이 변경되면 final 변수의 값이 변경되었는데 프로그램에서는 이상한 짓을 하는 원인을 찾기가 상당히 어려웠던 경험을 하고 난 후 부터 이런 코드는 냄새나는 코드로 여겨지게 되었다.

    static은 글로벌변수와 동일하므로 남발해서는 안된다. static을 사용할 경우 다음 두가지는 최소한 기억한다.

    1. static field는 final의 경우와 달리 정말 "하나여도 되는지" 여부를 잘 생각해야 한다.
    2. static method는 주저하지 말고 쓰되 다음 두가지의 경우 매우 활용적이다.
      1. 다른 많은 클래스에서 사용하는 Utility Method 군을 만드는 경우. (주로 Utility Class의 method)
      2. 클래스 안에서만 사용하는 "하청 메소드(private method)". 이유를 예를 들어 설명하면, 아래와 같은 조금은 과장된 클래스가 있다고 하자.

                    public class T ..                    private int a;                    private int b;                    private int c;                                        private int calc(){                        c = a + b;                        return c * c;                    }                       ....other method or getter/setter...

    위의 클래스 T의 경우 내부에서 calc라는 instance 함수를 사용하게 되면 c 의 값이 매번 변하게 된다. 이는 무심코 하는 실수로 클래스내에서 private method는 모든 멤버 instance 변수에 접근 가능하게 되면서 발생한다. c의 값이 변하지 않기를 바랄수 있다. 이때 안전한 방법은 다음과 같이 calc 하청 메소드를 static method로 수정하면 안전하다.

                private static int calc(int a, int b){               int c = a + b;               return c * c;            }

    여기서 a와 b는 멤버 변수를 접근할수 없어 전달해야한다.(static에는 this가 없어 instance field를 참조할 수 없다는 것은 이미 위에서 설명했다.) 또한 c도 같은 이유로 사용할 수 없어 로컬 변수로 선언하고 사용하고 있다. 이럴 경우 메소드가 약간 커질수 있지만 instance member 변수를 안전하게 사용할 수 있다는 장점이 있다. 이것은 static을 다시한번 생각하게 하는 좋은 예가 되었을 것이다.

    2 Java는 Pointer언어이다? (Java에는 Pointer밖에 없다?) #

    2.1 Java는 primitive형을 제외하곤 모두 Pointer이다 #

    "Java에는 포인터가 없다" 라고 Java의 장점 이라고 생각하는 것은 입문자도 외우고 있다. 하지만 이 부분은 의외로 Java를 혼란스럽게 하는 주범이라고 생각한다. Java에 포인터가 없기는 커녕 primitive(int, short, char, long...등 JVM의 Heap에 object로 생성되지 않는것들)를 제외하면 "포인터 밖에 없는 언어이다"라는 명제가 성립되게 된다. 사실 여기서 포인터라고 함은 C 의 그것과는 조금 다른 reference(참조)이긴 하지만...

    "즉, 자바의 클래스형의 변수는 모두 포인터이다."

    2.2 null은 객체인가? #

    Java에서 공참조(힙에 실제로 참조되는 object가 없는 참조)의 경우는 당연히 객체가 붙어 있지 않다. 그러나, Java API 레퍼런스의 NullPointerException 항에는 다음과 같이 기술되어 있다.

    "object가 필요한 경우 application이 null을 사용하려고 하면 throw된다. 가령 다음과 같은 경우이다."
    • null object의 instance method 호출
    • null object의 field(member variables)에 대한 액세스 또는 그 값의 변경
    • null의 길이를 배열처럼 취득할 경우
    • null의 slot을 배열처럼 액세스 또는 수정
    • null을 Throwable처럼 throw 할 경우

    위에서 null object라는 말이 등장하는데 이는 공참조에 객체가 붙어 있지 않은 것이 아니라 null을 가리키는 객체라고 볼수 있다. 즉, 공참조라는 것은 JVM에서 봤을때 아무것도 참조하지 않는것이 아니라 null이라고 하는 object를 참조하고 있는것이다. 그러나 JSL 4.3.1에서는 다음과 같이 나와있다.

    "참조값(reference)은 이러한 객체의 포인터나 어떤 객체도 참조하지 않는 특수한 null참조가 된다"

    즉, 공참조는 어떤 객체도 참조하지 않는다고 단정하고 있다. 하지만 '==' 연산에 있어 두개의 객체가 모두 null이거나 동일한 객체 또는 배열 참조의 경우 true라고 되어있는것으로 봐서 서로 다른 두 객체가 동일한 null을 참조하고 있으므로 true가 된것이 아닌가 하는 생각을 할 수 있다.

    즉, null이 Object의 instance 형태는 아니지만 개념적으로 봤을때 null도 object라고 봐야 하지 않을까?

    2.3 String에 대하여 #

    String Object에 대한 생각.

                String str = "111222";            String a = "111";            String b = "222";            String c = "111";            String d = b;            String t = str.substring(0,3);  //111

    위의 소스를 보고 다음이 참인지 생각해 보자. (==연산자는 포인터의 비교이지 값의 비교가 아님)

    1. str == (a + b) ==> 이것은 두개의 참조와 하나의 참조를 비교했으므로 당연히 false이다.
    2. a == b ==> 이것은 당연히 false
    3. d == b ==> 이것은 동일한 reference이므로 true
    4. a == t ==> a 와 t 는 둘다 값이 "111"이다. 하지만 이것은 서로 다른 참조를 가져 false이다. 그렇다면 다음 5번도 false일까?
    5. a == c ==> 이것은 true이다. 아.. 4번과 혼란스럽다. 이것이 참인 이유는? ==> 이것의 해답은 JSR 3.10.5에 다음과 같이 나와 있기 때문이다.

    "동일한 내용의 문자열 리터럴에 관해서는 인스턴스를 하나밖에 생성하지 않는다."

    즉, 위의 a와 c 는 '=' 를 이용하여 문자열 리터럴을 생성하게 되는데 a 에서 이미 만들어 놓았으므로 c에서는 그것을 참조한다.

    2.4 객체지향의 캡슐화 파괴 주의 #

    "object pointer를 반환하는 getter method는 객체지향의 캡슐화가 파괴될 가능성이 있다." 이는 object형의 field(member variable)의 getter에서 이 object를 그냥 반환하면 이 object를 받은쪽이나 참조하고 있는 다른쪽에서 이 object의 내용을 변경하게 되므로 사실 캡슐화(은닉)는 이루어 지지 않았다고 봐야한다.

    "이럴 경우 object를 clone(복제) 하여 반환하지 않아도 되는지를 반드시 생각해 본다."

    object의 복사에는 shallow copy와 deep copy가 있다.

            //(참고)Member에는 두개의 field(Identity Class 형의 ID와 Family Class 형의 family)가 있다.                 /** shallow copy */        Member shallowCopy(){            Member newer = new Member();            newer.id = this.id;            newer.family = this.family;                        return newer;        }                     /** deep copy */        Member deepCopy(){            Member newer = new Member();            newer.id = new Idetity(this.id.getId(), this.id.getName());            newer.family = new Family(this.family.getFamilyName(), this.family.getFamilyInfo());                        return newer;        }        

    위 소스에서 보듯이 shallowCopy 는 object를 복사하여 반환한것 처럼 보이지만, 사실은 Member object만 새로 생성되었을뿐 Member의 field는 newer와 this 둘다 서로같은 힙의 id와 family를 참조한다. 하지만 두번째 method인 deepCopy의 경우 Member의 field를 새로 생성하여 복사하므로 서로 다른 id와 family이다.

    "Java에서는 clone이라는 method가 준비되어 사용되는데 이는 기본이 shallow copy임을 명심해야 한다. deep copy를 사용하기 위해서는 clone method를 overload하거나 따로 만들어 직접 기술해야 한다." (참고) object를 immutable(변하지 않는, 불변의 객체)로 만드는 요령
    1. 모든 field(member variable)를 생성자(constructor)를 이용하여 초기화 한다.
    2. 모든 field는 private으로 선언하고, getter method는 만들되 setter는 기술하지 않는다.

    즉, 값을 변경하기 위해서는 object를 다시 만들어야만 하는 불편은 있지만 안전하게 사용하려 할때 유용하다.

    2.5 배열에 대하여 #

    2.5.1 배열은 object 인가? #

    JVM에서 배열은 object로 취급되어 object와 같이 aload, astore와 같이 bytecode로 기술되어 진다. int[] iarr = new int10; 에서 보는것과 같이 new로 Heap 영역에 object를 생성하므로 object임을 알 수 있다.

    2.5.2 배열의 length는 왜 field(member variable)인가? #

    String의 길이를 구할때는 length()와 같이 method를 이용하는데 배열은 object임에도 불구하고 legth와 같이 필드로 되어있다. '이래도 Java가 완전한 객체지향 언어인가' 라는 의심이 들게 한다. 그렇다면 length가 public이므로 array.length = 100; 과 같이 하면 배열 크기가 변경되나?

    이것은 컴파일 오류가 난다. length는 final이라 값을 변경 할 수 없다는 것이다. 그렇다면 final field로 한 이유는 무엇이냐는 Java News Group에 찾아보면 대부분이 "효율을 위해서"라고 되어 있다. JIT compiler를 사용하지 않는한은 method보다는 field가 빠른건 당연한 것이다.

    그런데 정말 알수 없는것은 byte code에서는 arraylength라는 전용명령으로 컴파일 된다. 즉, length는 Java의 문법이 어찌되었든 JVM레벨에서는 field가 아닌것이 분명하다. 그렇다면 효율을 위해서 field로 했다는 것은 도데체 무슨 소리인가?

    전문가들의 대답에는 이것은 Java의 수수께끼 중 하나라고 대답하는 사람이 많다고 한다.^^;

    2.5.3 final과 배열에 대하여... #

    우리가 흔희 앞에서도 나온바 있지만 final은 값을 변경할 수 없는 것이라고만 생각하지 object로 되어 있을 경우 그 object는 변경 가능하다는 것을 잊곤한다. 배열도 object이므로 마찬가지다.

    final int[] iarr = new int[5]; 일경우 iarr = null; 은 에러가 나지만 iarr[3] = 5; 는 에러가 나지 않는다. 즉, final이 지정되어 있는것은 iarr이지 iarr이 가리키는 곳 배열의 요소가 아닌 것이다.

    2.5.4 "Java에서의 다차원 배열은 존재하지 않는다." #

    가령 2차원 배열 처럼 보이는 int[][] iarr 또는 int[] iarr[] 은 일차원 배열 두개이지 2차원 행열 구조가 아닌것이다. 즉, 두개의 배열은 각각이 배열로 되어 있는 것이지 테이블(행열)형태가 아니다.

    2.6 인수(parameter/argument)전달의 개념 #

    2.6.1 "Java에서 parameter(argument) 전달은 무조건 'call by value' 이다" #

    primitive type의 경우 호출한 쪽의 변수값은 호출 받은 method내에서 값이 변경되어도 변경되지 않는다. reference type의 경우도 reference되는 object에 대해서는 함께 변경되지만 reference pointer는 call by value이다. object를 가리키는 pointer는 call by value로 변경되지만 Heap의 실제 object내용은 변경되지 않는다.

    2.6.2 "C와 같은 언어는 static linking이지만, Java는 dynamic linking이다." #

    따라서 Java는 Class 파일이 처음에 한꺼번에 memory에 읽혀지는 것이 아니라 런타임시에 그것이 필요해 졌을때 읽혀지고 링킹된다. static field의 영역도 Class가 읽혀지는 시점에 비로서 확보된다. 이렇게 되면 최초 가동시간이 단축되고 끝까지 사용하지 않는 Class의 경우 신경쓸 필요가 없어지게 된다.

    따라서 static field는 프로그램이 시작되어 해당 Class가 필요해 졌을때 JVM이 알아서 load/link 해 준다. 즉, static field는 프로그램이 실행되기 시작할 때부터 끝날때까지 계속해서 존재하는 것이라고 보면 된다. (참고) 링킹(linking)의 의미

    link된다는 것은 Class가 memory에 loading될 때 특정 메모리 번지에 loading되는데 이 메모리 번지는 loading될때 마다 다른 번지수에 loading된다. 이때의 메모리 주소값(Java에서는 실제 메모리 값이 아닐 수 있다)을 현재 실행중인 프로그램에서 알 수 있도록 하여 해당 Class에 대한 참조가 가능하도록 연결하는 과정이다.

    정적(static) link라는 것은 이러한 메모리에 대한 주소 정보를 컴파일 시에 compiler가 미리 결정하는 것이고, 동적(dynamic) link라는 것은 프로그램 수행 중 결정되는 것을 의미한다. 정적인 link의 경우 직접적으로 메모리의 번지값이 할당 되는 것이 아니라 offset값(기준위치로 부터의 index값)으로 연결시킨다.

    2.7 GC 에 대하여 잠깐! #

    2.7.1 "Garbage Collection은 만능이 아니다." #

    Java에는 free가 없다. GC가 알아서 해준다. 하지만 GC 수행중에는 프로그램이 멈추는 것과 동일한 증상이 나타나기 때문에 GC가 자주 발생하지 않도록 프로그램 해야 한다. 서비스 되고 있는 시스템에서도 가끔 시스템이 응답이 늦어지는 시점이 있는데, 이는 GC가 수행되고 있는 중이 대부분이다.

    그렇다면 GC가 자주 발생하지 않도록 해야 하는데 가장좋은 방법은 무엇일까? 그것은 바로 불필요한 객체를 생성하지 않는 것이 아닐까?

    개인적으로 Java에 free가 없는것이 너무나 든든하게 느껴진다. 이유는 두개의 변수가 Heap내의 하나의 object를 reference하고 있을 경우 실수로 하나의 변수만 free해 버리면 나머지 하나는 dangling pointer라하여 reference pointer가 모르는 사이데 사라져 버려 곤경에 처하는 것을 예방해 주기 때문이다.

    참고로 Object class에는 finalizer라는 method가 있어 GC 수행시점에 호출되는 method가 있지만 이것은 GC가 언제 수행될지 알 수 없으므로 과신하지 말아야 할 것이다.

    2.8 Java Pointer 결론 #

    2.8.1 "결국 Java에는 pointer가 있는 것인가, 없는 것인가?" #

    Java는 Heap내의 Object를 참조(reference)하고 있고, 참조는 결국 개념이 포인터와 같은 것이므로, "Java에는 pointer가 없다"는 것은 어불성설이다.
    // 이부분에 대해 Object를 이해하시면 족히 이런 문제는 사라질것으로 봅니다.
    // 클래스에 대한 인스턴스(object)들은 reference로 밖에 가질(참조될)수 없기 때문입니다.
    // 컴파일러 입장이 아닌 언어 자체의 사상을 가지고 쉽게 이해시키는 것이 좋을것 같습니다.

    JSR 4.3.1을 보면 다음과 같은 말이 나온다.

    "참조값(reference)은 객체의 pointer이거나, 또는 어떠한 객체도 참조하지 않는 특수한 null 참조가 된다"

    또한 java.sun.com의 Java programmer's FAQ에 "Java는 pointer가 없다고 하는데, linked list는 어떻게 만들어야 하나?"라는 질문에 다음과 같이 답변이 나와있다.

    (답변) Java에 관한 많은 오해중에서 이것이 가장 심각한 것이다. 포인터가 없기는 커녕 Java에 있어 객체지향 프로그래밍은 오로지 pointer에 의해 행해진다. 다시 말새 객체는 항상 포인터를 경유해서만 access되며 결코 직접적으로 access되지 않는다. pointer는 reference(참조)라고 불리며 당신을 위해 자동으로 참조된다.

    "Java에는 pointer가 없고 주장하는 모든 서적과 글들은 Java의 reference사양에 모순된다고 봐야 할 것이다."

    3 상속과 interface의 문제점 #

    3.1 상속 #

    3.1.1 상속에 있어서의 생성자(constructor) #

    "child의 default 생성자가 있으면 그 생성자에는 parent의 생성자(super()) 호출이 compile시 자동 삽입된다." 따라서 super() 이전에 다른 코드가 있으면 object가 생성되기 이전이므로 오류를 발생하게 된다.

    3.1.2 "down cast는 본질적으로 매우 위험하다" #

    down cast - child의 type으로 parent를 casting - 는 parent 형태의 type이 정말 child type인지 compile시에는 알 수 없다. 실행시에 type check가 이루어 지므로 runtime시에 ClassCastException이 발생할 가능성이 커진다.

    "프로그래밍시 오류는 가능한한 compile시에 처리하는것이 좋다."

    3.1.3 "추상클래스에 final이 있으면 compile error이다" #

    abstract method가 있는 클래스는 추상 클래스이고 추상클래스는 상속되지 않으면 아무런 의미가 없기 때문이다.

    3.2 interface #

    3.2.1 "interface는 interface일뿐 다중 상속의 대용품이 아니다." #

    interface를 method signature - 추상클래스와 같이 구현부는 없이 선언부만 있는 method - 의 용도로 생각하는것이 Java에서는 옳다. 즉, interface는 final field와 abstract method가 있는 클래스와 동일하긴 하지만 상속의 의미와는 그 용도가 다르다. 공통된 type을 정의하는것으로 보는것이 맞는 의미일 것이다.

    또한 interface는 클래스를 재이용하기 위해 상속을 사용하여 캡슐화의 파괴를 수반하는 것을 방지하는 기능이있다. 상속을 사용하면 모두 구현후 마치 소스 코드가 여기저기 천 조각을 주워 모아 만든 '누더기'같이 보이는 것에 한숨을 쉰 경험이 있을 것이다. 이 부분을 interface로 구현하면 보다 깔끔한 코드가 나오게 된다. 물론 public과 protected를 적절히 잘 사용해도 되긴 하지만 말이다.

    하지만 상속은 메소드 오버라이드한 경우 클래스를 마음대로 개조해 버린 셈이 되므로 어디선가 묘한 모순이 발생하게 될 가능성도 높아질뿐 아니라 추상클래스의 경우 실제 구현부가 어디에 위치하는지도 에매하게 느껴질 수 있어 불안한 코드가 되고 만다.

    3.3 상속 제대로 사용하기 #

    "그렇다면 제대로 된 상속은 어떻게 판단할 수 있을까?"

    상속은 'is a'관계가 성립해야 올바르다. 즉 '서브클래스(자식) is a 슈퍼클래스(부모)'가 성립해야 한다. 예를 들면 Red is a Color는 올바른 명제이지만 Engine is a Car는 'has a'관계이므로 상속이라고 볼 수 없다. "따라서 'has a'관계는 상속이 아니므로 composition과 delegation을 이용하면 된다."

    composition은 '객체를 field가 갖게 하는 방법'을 의하므로 'has a'관계가 정확히 성립한다. "상속 대신 composition과 delegation(조작이나 처리를 다른 객체에 위임)을 사용하면 다음과 같은 이점이 있다."

    1. 상속에서는 슈퍼클래스가 허용하고 있는 조작을 서브클래스에서 모두 허용해야 하지만, composition과 delegation에서는 조작을 제한할 수 있다.
    2. 클래스는 결코 변경할 수 없지만, composition하고 있는 객체는 자유롭게 변경할 수 있다. 예를 들면 학생 클래스가 영원이 학생이 아니라 나중에 취직을 하여 직장인 클래스가 될수 있다.
    상속을 composition과 delegation으로 변경하는 요령은? 여기서 Shape를 상속한 Polyline과 Circle을 변경한다면 다음과 같다.
    1. Shape(부모)의 공통된 내용을 구현한 구현 클래스(ShapeImpl)를 만든다.
    2. Polyline과 Circle 클래스에서 ShapeImpl을 composition하고 부모와 공통되지 않는 method를 각각 위임 받는다.
    3. ShapeImpl 클래스의 method를 추출한 ShapeIF interface를 작성하고 Polyline과 Circle에서는 implements 한다.

    4 package와 access 제어에 관한 이해 #

    4.1 package #

    4.1.1 "package는 '계층구조' 인가?" #

    처음 Java를 접하면서 package에 대해 이해할때 마치 파일시스템과 같은 계층구조라고 이해하게 되어 ' import /test/*.class '는 왜 안되는지 의아해 했던 기억이 있다. 그리고 부모 directory에 있는 클래스에서 왜 자식 directory에 있는 Class를 import없이 사용할 수 없는지도 이상했다.

    즉, package에서 동일 부모라도 서로 다른 package는 완전히 별개의 package였던 것이다. 이 부분에 관해서는 JLS 7.1 에서 다음과 같이 기술되어 있다고 한다.

    "package가 계층적인 이름 구조로 되어 있는 것은 관련된 package를 일정 규약에 따라 체계화하기 위해서이고, package 안에서 선언되어 있는 top-level형과 동일한 이름을 가진 서브 package를 갖는 것이 금지되어 있는 점을 제외하면 특별한 의미는 없다."

    즉, Java에서는 package이름을 계층적으로 명명할 수 있을뿐 package구조 자체에는 어떤 계층적인 의미 부여도 할 수 없는 것이다. 다시 말해서 Java에서는 package이릉을 계층적으로 명명할 수 있을 뿐 구조자체는 평평한 것이다. 실제로 바이트 코드의 내용을 보면 깨어진 내용중에 java/lang/String과 같이 완전한 한정이름을 class이름으로 사용됨을 알 수 있다.

    4.1.2 "compiler 가 인식하는 class검색 순서(소스코드내 클래스가 발견될 경우 그 클래스의 위치를 찾는 순서)" #

    1. 그 class자신
    2. 단일형식으로 임포트된 class
    3. 동일한 패키지에 존재하는 다른 class
    4. 온디멘드 형식(..* 형태) 임포트 선언된 class

    4.2 access 제어 #

    public은 다른 package에서 참조 가능하고, 무지정할 경우 동일한 package내에서만 참조 가능하다.

    4.2.1 "interfacde member의 access 제어" #

    interface의 member field/method는 모두 public이다. interface member에 protected나 private을 지정할 수는 없다. 또한 public을 지정할 필요도 없다. 지정해도 상관없지만 JLS 9.4에서는 다음과 같이 명시되어 있다.

    "interface의 method에 대하여 public 수식자를 지정하는 것이 허용되고 있지만, style로서는 전혀 권장할 수 없다."

    즉, interface member는 모두 public이라 되어 있는 것이다. 또한 James Gosling도 집필에 참가한 '프로그래밍 언어 Java 3판'에서는 다음과 같이 기술되어 있다고 한다.

    "public이 아닌 member를 interface에게 갖게 하는 것은 아무런 의미가 없다. interface의 member에 대한 access제어에 interface 자신의 access 제한을 적용하는 것이므로 이것은 아무런 의미가 없다."

    4.2.2 그렇다면 interface를 다른 package에 대하여 숨기고 싶으면 어떻게 하는가? #

    그것은 interface 자체 선언에 public이 아닌 것을 적용하면 되는 것이다. member별로 제어할 수 없어 불편한 면도 있지만, 나름대로 그럴 듯한 규칙이다. 하지만 이것은 정말 이상한 논리가 아닐수 없다. public이 아닌 interface에 public method가 무슨 의미가 있는지 알 수 없기 때문이다. 이 interface를 구현한 클래스에서도 method는 모두 public이 되어야 하는데, 이것도 아무래도 이상하다.

    5 기타 Java 기능 #

    5.1 Thread #

    5.1.1 "Multi Thread에서는 흐름은 복수이지만 data는 공유될 수 있다." #

    Multi processing에서는 흐름은 복수이지만 data는 독립되어 있다. 하지만 Multi Thread에서는 Heap과 static영역에 관한 한 2개 이상의 Thread 사이에 공유가 이루어 진다. 따라서 2개 이상의 Thread에서는 동일한 static field를 참조할 수 있고, 동일한 객체에 접근할 수도 있다. 그러나 stack만은 Thread별로 독립되어 있다. stack은 method에 들어가는 시점에 확보되고 빠져 나오는 시점에 확보되고 빠져 나오는 시점에 Free 되므로 2개 이상의 Thread에서 공유할 수는 없는 것이다.

    5.1.2 "Thread는 객체와 직교하는 개념이다." #

    Multi Thread는 어디까지나 Thread라는 처리 흐름이 여러 개 존재할 수 있다는 의미이다. 요약하면 다음 3가지 이다.
    1. Multi Thread에서는 Thread라는 처리 흐름이 2개 이상 존재할 수 있다.
    2. 어떤 Thread에서 움직이기 시작한 method가 다른 method를 호출 했을때 호출된 측의 method는 호출한 측의 method와 동일한 Thread에서 동작한다.
    3. Thread의 경계와 객체의 경계는 전혀 관계가 없다. 즉, Thread와 객체는 직교하고 있다.

    5.1.3 "Synchronized 의 이해" #

    Multi Thread 기반의 programming시에 synchronized를 놓쳐 자주는 일어나지 않으나 뭔가 잘못되어 가는것을 경험한 적이 있다. 즉, 이것이 원인이 되어 버그가 발생한 경우 그 버그는 재현성이 지극히 낮아지기 때문에 꽤 고생을 하게 된다. 이런 사태가 발생하게 되면 버그의 원인을 찾기가 어렵게 되고 해당 application은 언제 발생할지도 모르는 오류가 있는 상태 그대로 운영되기 때문에 심각성이 내포되어 있다고 할 수 있다.

    이러한 사태를 방지하기 위해서는 critical section을 2개 이상의 Thread가 동시에 실행되지 않도록 '배타 제어'를 해야한다. 그 키워드가 바로 synchronized이다.

    synchronized에는 synchronized(obj){} 형태와 method에 synchronized 를 붙이는 두가지 방법이 있는데, 이 둘은 범위만 같다면 같은 의미이다. 예를 들어 설명하면, 아래의 소스에서 method1()과 method2()는 동일하다.

            synchronized void method1(){            ...        }                void method2(){            synchronized(this){                ...            }        }

    이렇게 동일한 의미를 두가지로 만든것은 method단위로 synchronized를 걸 일이 그만큼 많다는 것을 의미한다. 많이들 오해하고 있는 부분이 위의 소스에서 알수 있듯이 method에 synchronized를 사용한다는 것은 '그 객체에 해한 조작은 동시에 하나의 Thread라는 것이지 method 호출이 하나의 Thread가 아닌것이다'

    그렇다면, Thread A가 obj의 Lock을 설정하고 있는 상태에서 다시 한번 Thread A 자신이 obj의 Lock을 설정하면 어떻게 될까? 이 경우 Thread A는 이미 이 obj에 대하여 Lock을 보유하고 있으므로 기다리지는 않아도 된다. 위의 소스에서 method1에서 method2를 호출한다면?

    method1에서 이미 obj의 Lock을 보유 했으므로 method2의 synchronized(this) 부분에서는 Lock을 기다리지 않아도 된다.

    즉, Lock의 기준이 특정Thread에 있어서 Lock의 기준이 method가 아닌 object인 것이다. 이 규칙 덕분에 synchronized method도 재귀호출이 가능해지고, synchronized method가 동일한 instance의 synchronized method를 호출할 수 있는 것이다.

    주의할 점은 static method에 synchronized가 있다면 static은 this참조가 없다고 위에서 설명하였으므로, 이 클래스의 Class 객체를 Lock하게 된다. 기준이 xx.Class가 되는 것이다.

    5.1.4 "Thread 사용법의 정석은?" #

    Thread 사용법에는 다음 두가지의 정석이 있다.
    1. Runnable을 implements하고 Thread의 참조를 보유(composition) 하는 방법. 이경우는 단지 Runnable만 implement함으로서 해결되는 경우가 대부분이긴 하지만, 그 class 내에서 해당 class의 Thread를 조작하게 된다면 composition한 Thread 객체에 delegation하면 된기 때문이다.
    2. Thread class를 상속하는 방법. JDK의 소스를 보면 Thread class에는 Runnable을 implements 하고 있다. 그리고 run method는 native method이다. 따라서 Thread를 상속한 모든 클래스는 사실 Runnable을 implements하고 있는 것이다. run method는 abstract가 아니므로 구현되어 있고 우리는 이를 오버라이드하여 사용하고 있다. 이 방식을 사용하면 Thread의 method를 안팍으로 자유롭게 호출할 수 이지만, 이미 다른 class를 상속하고 있다면 이 방식을 사용할 수는 없다.
    JDK API Reference의 Runnable에 과한 설명중에 다음과 같은 내용이 있다.

    "Thread class의 method중 run method만을 오버라이드하여 사용하는 경우는 Runnable interface만 implements하여 사용하면 된다. 왜냐하면, class의 기본적인 동작을 수정 또는 확장하지 않는한 그 class를 sub class화 하는 것은 바람직하지 않기 때문이다."

    그렇다면 위에서 언제나 1)번 방식을 사용하면 되는 것 아닌가 라는 의문이 생기게 된다. 왜 귀찮게 2)의 방법을 고민하는 것인가, 극단적이긴 하지만 만일에 사태에 이 클래스가 다른 클래스를 상속받게 되는 경우도 있을수 있는데.

    하지만 이것은 아닐것이다. 만약 이렇다면 Thread class가 Runnable을 implements할 필요가 없었을 것이기 때문이다. 또한 Thread는 생성자의 인수로 Runnable의 reference를 취득한 후 계속해서 그것을 보유한다는 것도 이상하다. Thread에 있어 Runnable이 필요한 것은 start() 때 뿐이므로 start()의 인수로 Runnable을 건네줘도 좋을 것이다.

    그럼에도 불구하고 굳이 Thread에서 계속적으로 Runnable을 보유하고 있는 것은 Runnable객체와 Thread를 강하게 결합시키려는 의도 때문이다. 이것은 의도적으로 위의 2)의 방법을 권장하는 듯한 느낌을 받게 하는듯 하다.

    그렇다면 API Reference의 말은 단지 상속을 피하라는 의미만 있는 것인가? 마지막으로 한가지 추정이 되는 부분은 Thread에는 suspend()나 stop()등과 같은 method가 현재 모두 deprecate되었다. 또한 sleep()이나 yield()는 모두 static method이므로 굳이 Thread 객체를 보유할 필요가 없다.

    그렇다면 위의 1)의 방법에서 Thread객체를 composition할 필요가 없어진다.

    "그렇다면 Thread를 아무도 보유하지 않고 Runnable만 implements한 방식이 최선인가?"

    무엇이 정답인지 도무지 알길이 없다. ^^;

    5.2 Exception #

    5.2.1 "finally 절은 반드시 어떠한 경우에도 실행되는가?" #

    try ~ catch 문의 finally 절은 'loop라면 break, method라면 return 절'을 만나도 뒤에 있는 finally절은 수행된다. 하지만 다음의 경우는 그렇지 않다.

            try{            ...            System.exit(1);        }catch(...){        }finally{            ... //이 부분은 실행되지 않는다.        }

    5.2.2 "예외의 종류 3가지 (Error, RuntimeException, 그밖의 Exception)" #

    5.2.2.1 Error #
    이에 관해선 JLS 11.2.1에 다음과 같이 기술되어 있다. "체크되지 않는 예외 클래스(Error와 그 Sub class)는 프로그램안의 다양한 위치에서 발생할 가능성이 있으며, 회복이 불가능하기 때문에 컴파일시 체크되지 않는 것이다. 이러한 예외를 프로그램에서 선언한다고 해도 난잡하고 무의미한 것이 될 뿐이다."

    Java의 클래스 librury에서 Error의 sub class를 살펴봐도 AWTError, LinkageError, ThreadDeath, VirtualMachineError 등 'catch해도 소용 없을 것' 들 뿐이다. (OutOfMemoryError는 VirtualMachineError 아래에 위치한다.)
    5.2.2.2 RuntimeException #
    위의 Error 이외의 Exception들은 application에서 catch할 가능성이 있는 예외들이다.(버그가 없으면 발생하지 않는 예외들) 그리고 RuntimeException은 '어디서든 발생할 가능성이 있는 예외'이다. RuntimeException의 sub class로는 NullPointerException, ArrayIndexOutOfBoundException, ClassCastException 등을 들 수 있다. '이러한 예외는 버그가 없는 한 발생하지 않으므로 일일이 throws 를 작성하지 않아도 된다.'

    프로그램에 버그가 없는 한 발생할 수 없는 예외가 발생한 경우 C 언어와 같이 영역 파괴가 일어나기 쉬운 언어라면 프로그램 전체를 종료시키는 것이 정답이겠지만, Java와 같이 영역파괴가 일어나지 않도록 실행시 체크(JVM Classloader의 formal verification process)를 하고 동적으로 프로그램을 load하는 언어에서는 국소적인 NullPointerException 때문에 프로그램 전체를 중지시켜서는 안 될 것이다.

    따라서, RuntimeException은 catch하지 않는 것이 바람직하다고 볼 수 있다. 버그가 있는 프로그램은 신속히 종료시키는 것이 대부분의 경우 최선의 방책이라 생각하기 때문이다.
    5.2.2.3 그밖의 Exception #
    위의 RuntimeException이외의 Exception의 sub class는 사용자의 잘못된 조작 등으로 인해 프로그램에 버그가 없어도 발생할 가능성이 있고 그에 대하여 프로그램이 확실히 대응해야 하는 경우에 사용된다. 예를 들면 FileNotFoundException등이다.

    그런데 개발하다 보면 이상하고 의아한 것이 하나 있다. 숫자 부분에 문자를 넣었을때 발생하는 NumberFormatException이다. 이것은 이상하게도 RuntimeException의 sub class이다. 이것은 RuntimeException이 아니었으면 하는데 NumberFormat체크는 Runtime시에만 가능한 모양이다.

    5.2.3 "OutOfMemoryError는 어떻게 처리해야 하는가?" #

    예전에 Swing에서 Tree구조를 이용하는 프로젝트를 한적이 있다. 이때 Tree에 branch와 node가 무수히 생기자 JVM은 OutOfMemoryError를 내뱉었다. 이에 급한 마음에 OutOfMemoryError를 catch하여 사용자에게 재시작을 요청하는 Dialog를 띄우도록 수정하였다면 이 Dialog가 과연 떳을까? 현재 메모리가 부족한 판에 Dialog를 띄울 메모리가 남아있질 않았던 것이다. 다행히 Dialog가 떴어도 작업은 계속되지 못했을 것이다. NullPointerException가 나기 때문이다.

    원인은 나중에 찾았는데, Tree구조에서 부모부터 자식들을 붙이는 순으로 Tree를 구성하는데 자식들을 줄줄이 붙여나가다가 메모리 부족현상이 발생하였고 NullPointerException은 자식이 없으니 클릭하는 순간 null을 반환하여 발생하였던 것이다.

    OutOfMemoryError의 가장 좋은 해결책은 불필요한 객체를 만들지 않는 것이었다. 그리고 Tree생성시에도 자식부터 만들고 부모를 만드는 순서로 프로그램을 수정하여 프로젝트를 정상적으로 마칠수 있었다.

    마지막에 드는 심정은 프로그램이 OutOfMemoryError를 일으키는 원인이 과연 이렇게 구성되어 발생했는지 어떻게 알수 있을까 하는 의문이다.

    5.3 Object Serialize #

    Java에서는 ObjectOutputStream의 writeObject() method에 데이타 구조 저장소의 참조만 건네주기만 하면 그 안에 있는 모든 객체를 1차원 stream으로 출력해 준다. (파일이나 ByteArrayOutputStream을 이용한 메모리로) 단, static field는 Serialize되지 않는데 이는 Serialize의 대상이 instance 객체뿐이기 때문이다.

    5.3.1 "Serialize를 위해서는 marker interface인 java.io.Serializable interface를 implements해야한다." #

    여기서 marker interface는 java.lang.Cloneable과 같이 method와 field의 정의는 없지만 객체 Type을 위한 interface이다. 예전에 Serialize를 이용하여 데이타를 유지하는 프로젝트를 한 적이 있는데 그때 생각했던것이 '모든 class들이 기본적으로 Serializable을 implements하고 있으면 편할텐데..'라는 생각이었다. 하지만 이것은 상당히 위험한 발상이었다.

    Serializable이 기본으로 implements되어 잇으면 엉뚱한 객체까지 Serialize되고 그것을 알아채지도 못하는 사태가 일어날 가능성이 높다. Serializable이 optional인 이유는 이러한 이유 때문이리라..

    5.3.2 "super class는 Serializable이 아닌데 sub class만 Serializable인 경우의 문제점" #

    Serialize을 이용하여 프로젝트를 할때 한번쯤 실수할 수 있는 부분이 상속된 class의 Serialize이다. 컴파일 에러도 없고 Deserialize도 잘 되었다. 하지만 키가 되는 값이 null과 0이었다. 영문을 몰라 다른곳을 헤매여도 보다가 결국 찾은 원인은 부모의 field는 Serialize되지 않는다는 것을 알게 되었다. transient와 마찬가지로 형식별 default 값으로 채워졌었다. 이는 컴파일과 실행시 아무런 오류없이 실행되어 나를 힘들게 하였기에 Java가 원망스러웠던 기분좋은 추억이다. ^^;

    5.3.3 "transient field의 복원(?)관련" #

    Serialize를 이용한 프로젝트를 할때는 writeObject와 readObject를 이용하여 기본적으로 제공하는 Serialize를 customizing할수있다.

    Serializable에 대한 API reference에도 다음과 같이 나와있다.

    "Serialize와 Deserialize에 대한 특별한 handling을 위해서는 다음 두개의 특별한 메소드를 구현하면 된다."

    private void writeObject(java.io.ObjectOutputStream out) throws IOException;private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

    이 두 method가 private으로 되어 있는 것을 보고 처음에는 의아해 했었던 기억이 있다. 이를 protected나 public으로 하면 제대로 동작하지 않는다. 이는 override가 이니기 때문이다. 사실은 속에서 reflectiond을 이용하여 강제적으로 호출되고 있는것이다. reflection에서는 private method까지 찾을 수 있기 때문이다.

    또한 private으로 한 가장 큰 이유는 Serialize를 객체자신이 직접 해야 안전하다는 의미도 있지 않을까 하는 생각도 든다. 다시 본론으로 들어가서 transient를 복원하는 것에 얘기를 하자면, 사실 transient는 Serialize대상에서 제외되는 것인데 복원을 할 수 있다는 말이 안된다. 하지만 프로젝트를 진행하다 보면 logic상 가능한 경우가 많이 있다.

    즉, 모든 field를 Serialize하지 않고 필요한 것만 하고 특정 field는 Serialize한 field들을 이용하여 복원하는 방법이다. 또한 Serialize당시의 객체 상태와 Deserialize시의 객체상태가 서로 다를 수 있는 field도 그것에 해당된다. cafeid만으로 나머지 field는 DB에서 읽어오게 한다면 나머지 field는 transient로 처리하고 Deserialize시 readObject()에서 복원하는 것이다.

    5.3.4 "Stack Overflow에 주의하라!" #

    Serialize를 하다보면 참조로 연결된 객체를 recursive하게 거슬러 올라가며 이것이 너무 깊어지면 Stack Overflow가 발생한다. 가령 linked list같은 경우이다. 이것을 Serialize하면 그 요소수만큼 recursive 호출이 발생한다. 과거(JDK1.3.0시절) 프로젝트 당시 JVM이 5111에서 Stack Overflow가 발생했던 기억이 있다.

    물론 실행시 java option에 -Xss 를 이용하여 statck 크키를 조절할 수 있지만 이것은 개발자가 아닌 실행하는 사람들에게 부담이었다. JDK의 LinkedList class의 소스를 보면 writeObject()와 readObject()를 다음과 같이 변경하고 있다.

            private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {            s.defaultWrtieObject(); //이 코드는 무조건 들어가게 되는데 이곳 소스의 System.arraycopy()에서 overflow발생한다.                        s.writeInt(size);   //이부분이 실제 추가되어 Stack Overflow를 예방한다.                        for(Entry e = ...)                s.writeObject(e.element);            }            ...        }                //readObject()도 이와 같은 개념으로 변경되어 있다.

    5.4 "nested class / inner class / 중첩클래스" #

    5.4.1 "중첩클래스의 개념" #

    개인적으로 중첩클래스를 어떠한 경우는 사용하지 않으려 한다. 사용하기가 만만치 않고 코드 읽기가 힘들어 지기때문이다. 하지만 '어떤 클래스 내에서 은폐할 목적으로 사용하는 클래스가 있다면 이것을 사용해야 한다' 실제로 Java의 AWT 클래스 Event Handler를 비롯하여 많은 클래스에서 중첩클래스를 사용하고 있다. 또한 내부 class는 그것을 둘러싸는 class의 instance(enclosing object라고 하는)의 field를 참조 할수 있는것도 장점이다. 하지만 이는 내부클래스가 아닐경우 부부 클래스를 new해서 사용하는것과 별반 다를께 없지 않은가.

    5.4.2 "내부클래스는 부모의 참조를 몰래 보유하고 있다." #

    내부 클래스의 instance는 부모의 instance에 대한 참조를 몰래 보유하고 있기 대문에 위에서 얘기한 부모의 field를 참조할 수 있는 것이다. 그러므로 static method에서는 내부클래스를 생성할 수 없다. 다음 예를 보면 바로 알수 있다.

            class Test{            class InnerClass {                int i;                ...            }                        public static void main(String[] args){                InnerClass icls = new InnerClass();                ...            }        }

    이 소스를 compile하면 다음의 오류가 발생한다. "non-static variable this cannot be referenced from a static context..." main method는 static이므로 this를 참조할수 없다는 것이다. 이는 InnerClass가 new 되면서 외부 클래스 Test의 this를 보유해야 하는데 여기서 static을 만나니 오류를 표출시킨것이다. 물론 일반 instance method에서는 오류가 나지 않는다.

    5.4.3 "local inner class에 대하여" #

    local inner class라 함은 method내에서 선언된 inner class이다.

            public class OuterClass {            public int get(){                int i = 9;                int id = 99;                int id2 = 99;                final int id3 = 100000;                                class LocalInnerClass {                    int id = 100;                                        LocalInnerClass(){                        System.out.println("LocalInnerClass");                       }                                        int getId(){                        return id3 + id;                    }                }                                   LocalInnerClass lic = new LocalInnerClass();                return id + lic.getId();            }                           public static void main(String[] args){                OuterClass outer = new OuterClass();                System.out.println("id = " + outer.get());                  //결과 값은 "100000(id3) + 100(LocalInnerClass.id) + 99(OuterClass.get())" 인 100199가 나온다.            }                    }

    위 소스의 LocalInnerClass는 get() 이라는 method에서만 보이는 class이다. 그리고 특이할 만한 부분이 OuterClass의 get() method에서 final로 선언된 id3이 LocalInnerClass에서 참조 가능해 진다. id2를 참조하면 compile error가 나지만 final로 선언된 것은 오류가 나지 않는다.

    이는 local variable은 method에서 나오는 순간 사라지는데, local inner class는 local variable보다 수명이 조금더 길기 때문에 final만 허용한 것이다.

    5.4.4 "anonymous class(무명클래스)에 대하여" #

    무명 클래스는 말그대로 이름이 없는 클래스이다.

            class AnonymousTest {            private interface Printable {                void print();            }                        static void doPrint(Printable p){                p.print();            }                        public static void main(String[] args){                doPrint( new Printable(){                            public void print(){                                System.out.println("this is new Printable print()");                            }                         });            }        }

    위 소스의 "doPrint( new Printable(){" 부분이 무명클래스 이다. compile을 수행하면 AnonymousTest$Printable.class, AnonymousTest$1.class, AnonymousTest.class 세개의 클래스가 생긴다. 여기서 AnonymousTest$Printable.class는 Printable interface이고 AnonymousTest$1.class이 무명클래스이다.

    이 소스를 보면 처음에 드는 의심이 Printable interface를 new 했다는 것이다. 여기서 굳이super class(이 소스에서는 interface)를 저정해야 하는 이유는 아무것도 상속하지 않는 무명 클래스의 instance를 만들어 봐야 의미가 없기 때문에 이렇게 한듯하다.

    "무명클래스는 어떤 class나 interface를 상속/구현 해야만 그 instance를 사용할 수 있는 것이다" 이처럼 무명 클래스를 사용하면 어떤 절차(수행)를 다른 method의 인수로 건네줄 수 있게 된다. 하지만 간단한 로직만 구현처리해야 한다.

    "무명클래스는 조금만 복잡해져도 급격히 소스의 가독성이 떨어지게 되므로 남용하지 않는 것이 바람직하다"

    6 이래도 Java가 간단한가? #

    6.1 method overload 에서의 혼란? #

    6.1.1 "overload란 이름이 가고 인수가 다른 method에 compiler가 다른 이름을 붙이는 기능" #

    overload를 구현하면 bytecode로 변환시 다른 이름으로 method가 변환되어 별개의 method로 처리된다. 이를 JVM에서 method descripter라 하여 Oolong asembler로 변화시 다른 형태의 method가 된다. 예를 들어 "void get(double d, long l)" 은 "get(DJ)V"로 변경된다. 여기서 D는 double, J는 long, V는 void를 의미한다.

    그런데 여기서 "get(DJ)" 부분만 method 이름이므로 return type이 다른 동일 method는 overload 할 수 없다. 따라서 overload는 정적(compile시 결정)이라는 명제가 성립니다. 그래서 동적으로 사용되면 compile시 오류를 표출한다. 아래의 소스를 보자. 여기에는 IFS라는 interface와 이를 implements한 Impl1, Impl2 라는 class가 있다.

            //IFS.java        interface IFS {            public String getName();        }                //Impl1.java        class Impl1 implements IFS {            public String getName(){                return "Impl1";            }        }             //Impl2.java        class Impl2 implements IFS {            public String getName(){                return "Impl2";            }        }                   //main이 있는 OverloadTest.java        public class OverloadTest {                static void pr(int i){                System.out.println("pr_int : " + i);               }                        static void pr(String s){                System.out.println("pr_string : " + s);               }                        static void pr(IFS ifs){                System.out.println("pr_string : " + ifs.getName());            }                        static void pr_run(Impl1 i1){                System.out.println("pr_run : " + i1.getName());            }                        static void pr_run(Impl2 i2){                System.out.println("pr_run : " + i2.getName());            }                        public static void main(String[] args){                OverloadTest test = new OverloadTest();                test.pr(10);                test.pr("Jeid");                                   IFS ifs1 = new Impl1();                test.pr(ifs1);                                IFS ifs2 = new Impl2();                test.pr(ifs2);                                //pr_run(ifs1);                //pr_run(ifs2);            }        }

    위의 소스를 수행하면 정상적으로 compile이 될것인가?

    당연히 잘 된다. pr()은 overload를 잘 구현했다. 하지만 소스 하단의 두 주석문을 풀면 어떻게 될까? 이는 compile오류를 낸다.

            OverloadTest.java:36: cannot resolve symbol        symbol  : method pr_run (IFS)        location: class OverloadTest                pr_run(ifs1);                ^        OverloadTest.java:37: cannot resolve symbol        symbol  : method pr_run (IFS)        location: class OverloadTest                pr_run(ifs2);                ^        2 errors

    실제 위 둘의 pr_run method는 bytecode로 변환시 "pr_run(Lpackage_name.IFS)V"로 동일하게 생성된다. 따라서 compile시에 오류를 표출한다. 이 소스를 보면 알 수 있듯이 "method overload는 정적(compile시)으로 미리 결정되며, 동적(실행시판단)으로 사용할수 없다."

    6.1.2 "그렇다면 overload에서 실제로 혼동되는 부분은 무엇인가?" #

    다음 소스를 보고 실제로 수행되는 method를 찾아보라.

            class OverloadTest2 {            static int base(double a, double b){ ... }  //method A                        static int count(int a, int b){ ... }  //method B            static int count(double a, double b){ ... }  //method C                        static int sum(int a, double b){ ... }  //method D            static int sum(double a, int b){ ... }  //method E        }

    • base(3,4) 를 호출했을때 수행되는 method는? => 당연히 method A (3과 4는 정수라도 double이 되므로 정상적으로 수행됨)

    • count(3,4) 를 호출했을때 수행되는 method는? => B와 C중 갈등이 생긴다. 이럴경우 JVM은 가장 한정적(more specific)한 method를 찾는다. 여기서 3과 4는 정수형에 가까우므로 method B 가 호출된다.

    • count(3, 4.0) 을 호출했을때 수행되는 method는? => 이것은 4.0 이 double이므로 method C 가 더 한정적이므로 method C 가 호출된다.
    • sum(3,4.0) 을 호출했을때 수행되는 method는? => 이것은 당연히 type이 일치하는 method D.
    • sum(3,4) 를 호출했을때 수행되는 method는?? => 이런 코드가 소스내에 있으면 다음과 같은 compile 오류를 표출한다.

                 OverloadTest.java:48: reference to sum is ambiguous, both method sum(int,double)               in OverloadTest and method sum(double,int) in OverloadTest match                    System.out.println("sum(3,4) = " + sum(3,4));                                                       ^             1 error

    method D와 method E가 애매하다는 compile 오류이다. 이것은 둘중 어느것이 더 한정적인지 찾을 수 없으므로 bytecode 를 생성 할 수 없다는 것이다.

    "이렇듯 compiler에게 불필요한 오해(혼동)를 초래하는 overload는 사용하지 않는 것이 좋다. 개인적으로 overload를 가능한 사용하지 않으려 하고 필요하다면 인수의 개수가 다른 overload를 사용하는 편이다."

    6.1.3 (참고) 또다른 혼동, overload한 method를 override 하면? #

    overload란 compiler가 bytecode변환시 다른 이름을 붙이는 기능이라는 것을 위에서 설명했다. 따라서 super class에서 overload한 method를 상속하여 override하면 완전 별개의 method를 override한것처럼 JVM은 판단한다. 즉, overload와 override는 직교(전혀상관없는)하는 개념이다.

    6.2 상속/override/은폐 에서의 복잡함 #

    6.2.1 "Java class의 member 4 종류" #

    1. instance field
    2. instance method
    3. static field
    4. static method
    여기서 상속을 하였을 경우 runtime시 객체의 형식에 따라 선택되는 것은? 2번 instance method 뿐이다. 즉, 동명의 member를 sub class에서 선언했을 때 instance method만 override 되고 나머지는 완전 별개의 member가 된다. 따라서 위의 1,3,4는 sub class에서 동일하게 선언했을 경우 별개의 것으로 인식되며 compile시에 무엇을 access 할지 결정된다.

    즉, instance method는 override되지만 instance field/static field는 은폐된다. override는 실행시 객체의 형식에 따라 처리 할당되지만, 은폐의 경우는 compile시에 결정되고 만다.

    6.2.2 "override시 method 이름에 대한 함정" #

    과거에 코딩을 하던중 정말이지 어처구니 없는 경우를 당했다. override 하는 method이름을 잘못써서 황당한(?) 고생을 한적이 있다. super class의 writable()이라는 method를 writeable()이라고 override(?)하였는데 프로그램 수행 중에 writable()이 항상 false가 나오는 것이 아닌가? 그래서 소스를 추적추적 하다 몇시간을 허비했었던 기억이 있다.

    java를 접한지 얼마되지 않았고 요즘같이 eclipse같은 에디터도 없이 메모장에서 코딩하던 시절이라 더욱 고생했던것 같다. 한참 후에야 우연히 스펠링이 잘못된걸 알고 얼마나 황당했던지... 지금 생각하면 이것도 좋은 추억이리라.

    무조건 override 잘 되었을거라 생각 했던 나의 불찰도 있었지만 compile때나 runtime시 아무런 반응을 보이지 않던 Java도 원망스러웠다. 2003년도에 C#으로 프로젝트를 했는데 C#은 상속의 override에 대하여 "override void writalbe().."과 같이 정의시 override를 명시해야 된다는 것을 보고 상당히 마음에 들어 했던 기억이 있다. 가독성도 뛰어날 뿐더러 나의 몇시간동안의 헤메임도 없을 것이기 때문다. Java도 이렇게 확실한 명세였으면 정말 좋겠다.

    6.2.3 "또다른 나의(?) 실수 - 말도 안되는 오타" #

    위의 method이름을 잘못써서 고생하기 이전에 아주 비슷한 고생을 한적이 있다.

    '난 정말 바보인가'라는 생각을 들게 했던 문제였다. 초보 시절에는 왜이리도 오타가 많이 나던지... 요즘은 대충 키보드 두드려도 오타가 잘 안나는데 그 시절에 오타 때문에 느린 CPU에서 컴파일을 몇번을 했는지... 기억을 되살리면 소스는 다음과 같다.

            public class Member {            private int memberNo;                        public int getMemberNo(){                return this.memberNo;            }                        public void setMemberNo(int menberNo){                this.memberNo = memberNo;            }                        ......        }


    위 소스의 Member에는 다른 여러가지 member field가 있는데 DB의 member table에 memberid 컬럼이 memberno로 변경되면서 Member class의 memberId를 memberNo로 변경하게 되었다. 위와 같이 수정하여 배포해놓고 테스트를 하는데 시스템이 완전히 뒤죽박죽으로 돌아버리는 것이 아닌가. 이 경우도 method 이름처럼 몇시간을 헤매었다.

    이번에 argument의 오타로 인한 어처구니 없는 실수였다. setMemberNo(int menberNo)에서 문제가 발생되었던 것이다. 인수의 memberNo를 menberNo로 잘못친것이다. 그래서 memberNo에는 해당 member의 memberno가 아닌 0이 모두 들어갔어던 것이다. 시스템은 memberno를 기준으로 도는 부분이 너무나 많았기에 오류나는 부분도 많았으며 DB에서는 제대로 된 memberno을 읽어 왔으며, compile과 runtime시 아무런 반응도 없었기에, 초보자를 그렇게도 고생시켰나 보다.

    이것도 member field면 무조건 this를 붙이도록 하던지 Java가 인수는 'm_'와 prefix를 붙이도록 Coding Style을 정의- SUN사이트의 Java Coding 규약에는 "Variable names should not start width underscore_ or dollar sign $ characters, even though both are allowed." 와 같이 명시되어 있다 - 했더라면 발생하지 않았을 문제이다.

    또한 C언어나 C#에서 처럼 compile 경고레벨을 높여놓으면 "menberNo는 어디서도 사용하지 않습니다."와 같은 메세지를 보여 줬더라면 고생을 덜 하지 않았을까?

    6.2.4 "static member를 instance를 경유하여 참조해서는 안 된다." #

    예를 들어 ClassA 에 public static int AA 라는 static field가 있을 경우 ClassA.AA 로 접근해야 하는데, 다음과 같이 사용하는 실수를 범한다.(물론 오류는 없지만)

            ClassA a = new ClassA();         int i = a.AA;       //instance를 경유하여 접근        int j = ClassA.AA;  //올바르게 접근

    그럼 왜 굳이 ClassA.AA와 같이 instance가 아닌 class이름을 붙여야 할까?

    static member(static field/static method)는 compile시에 이미 어느것을 호출할 지 결정하기 때문에 위의 a.AA와 같은 것은 static이 아닌것 같은 오해와 혼란만 가져오기 때문이다. 심지어 개인적으로는 동일 class 내 - 위 소스에서 ClassA의 member method - 에서 ClassA.AA라고 사용하는 편이다.

    이는 local variable과 혼동될 염려도 없을뿐더러 AA라는 변수가 static이라는 것도 확실히 알 수 있기 때문이다. 물론 private static 의 경우는 ClassA.BB 와 같이 하지 않고 BB 라고 해도 무방하겠지만 말이다.

    6.2.5 "super keyword는 부모의 this" #

    Java 개발자 대부분은 'super' 에 대하여 그렇게 민감하지 않을 것이다. 그거 super() 나 super.method1() 과 같이 사용되지 그 이상에 대해선 깊이 생각하지 않게 된다. super를 한마디로 정리하면 다음과 같다.

    "super keyword는 instance method등에서 this를 사용할 수 있는 곳에서만 쓸 수 있다. this의 자리에 super라고 쓰면 현재 class의 member가 참조되는 대신 부모 class의 member가 참조되는 것이다."

    6.3 상속에 관한 또 다른 문제 #


    6.4 그밖의 함정 #

    6.4.1 "생성자에 void 를 붙인다면?" #

    생성자에 void를 붙인다면 그 class가 new 될때 그 생성자(?)가 실행될까?? 아래의 'Constuctor'라는 문자열은 출력될까?

            public class ConstructorTest{            void ConstructorTest(){                System.out.println("Constuctor");            }            .....        }

    출력되지 않는다. 물론 compile시 아무런 경고도 없었다. 즉, void가 붙은 ConstructorTest()는 생성자가 아니라 instance method일 뿐이었고 new시에는 default constructor가 실행 되었던 것이다.

    6.4.2 "if / switch 의 함정" #

    Java 개발자라면 대부분이 초보시절에 if 조건절에 '==' 대신 '='을 써본 기억이 있을것이다. 예를 들어 "if( isListenLecture == Student.STUDENT )" 를 "if( isListenLecture = Student.STUDENT )" 로 잘못 쓴 경우이다. 여기서 Student.STUDENT는 boolean type이다. 여기서 isListenLecture는 항상 Student.STUDENT 값을 갖게 되는 버그가 생긴다. 이는 compile시에 아무런 경고도 없다. 이렇게 한번 당하고 나면 앞으로는 '=='를 정확히 쓰게 되거나 아니면 다음과 같이 쓴다.

    "if( isListenLecture )" 또는 "if( !isListenLecture )" 라고 말이다. 이것이 더욱 간결하고 의미도 분명해 지기 때문이다. 또한 다음 소스와 같은 오류도 범하는 경우가 있다. 이는 잘못된 indentation으로 빚어지는 초보의 함정이다.

    이글을 읽는 분께 한가지 당부드리고 싶은것은 여기서 초보라고 다 그런건 아니라는 것이다.

            ....        if( a < 5 )            b = 3;            c = 10;   //이부분은 나중에 추가된 라인이다.                                if( isStudent )            if( isFemale )                sayHello("Hi~~");        else            sayHello("Hello Professor~");

    위의 소스중 c = 10; 이 if( a < 5 )의 참일때 수행된다고 오해할 수도 있고, sayHello("Hello Professor~"); 부분이 if( isStudent )의 else 부분이라고 오해 할 수도 있다. 이것은 전적으로 indentation(들여쓰기)의 불찰로 개발자가 잘못 읽을 수 있는 부분이다. Java Coding Style에서는 if문 다음에 한줄의 코드가 있더라도 {} 를 사용하길 권고한다. 그러면 첫번째 if문과 같은 오류를 방지할 수 있고 두번째 if문에서도 보다 가독성이 생길 것이다.

    이와 유사한 것으로 switch문의 case 절에서 break를 쓰지 않아 항상 동일하게 처리되는 버그도 경험해 보았을 것이다.

    7 Java 기능 적용 몇가지 #

    7.1 대규모 개발에서 interface 분리하기 #

    7.1.1 "interface 분리의 필요성" #

    Java와 같은 객체지향언어에서는 공개해야 할 method만을 public으로 하고, 공개할 필요가 없는 것은 private으로 하여 class의 상세한 내용을 은폐할 수 있게 되어 있다. 그런데 private 부분이 은폐되어 있는것 처럼 보이는가?

    소스를 보면 훤히 들여다 보이는데?

    대규모 개발은 하부 class부터 bottom-up으로 진행하는 것이 이상적인 형태일 것이다. 그런 형태로 개발하면 임의의 시점에서 테스트를 할 수도 있다. 그러나 현실적으로 단기간에 많은 수의 개발자가 붙어서 단시간에 개발을 진행하는 경우가 많다. 또한 서로 호응하는 관계에 있는 class들은 어느쪽이 하부인지 정의하기가 난감할때가 많다. 이런경우 우리는 흔히 package단위로 나누어 개발한다. 하지만 이럴경우 어느정도 코딩이 종료될때까지 테스트하기가 상당히 힘들어 진다. Java에서는 private member와 method 구현까지 하나의 파일에 코딩하는데 개발 중간에 공개하여 다른 개발자가 이용해야 하는 class를 배포할 수 없으므로 동시 개발이 까칠해 진다.

    이 상황에서 다른 package(개발자)에 공개해야 하는 class 부분을 interface로 공개하면 많은 부분 유연하게 된다. 이 interface를 다른 개발자는 개발을 하고 테스트가 필요하다면 TestImpl class를 만들어 하면된다. RMI나 CORBA에서도 Stub은 이런식으로 IDL을 정의한다.

    7.2 Java에서의 열거형 #

    Java에서는 열거형-C의 구조체, 공용체-이 없다. 열거형이 왜 필요하냐고 반문하는 개발자도 있을 것이다.

    하지만 열거형이 없어 곤란을 경험한 개발자도 꽤 있으리라 본다. 최근언어(특히 객체지향 언어) - Java, Eiffel, Oberon등 - 에는 열거형은 포함되어 있지 않다. C#에는 있긴 하지만.

    이런 이유로 Java AWT의 Label class는 다음과 같이 구현되어 있다.(텍스트의 정렬값관련)

            public static final int LEFT = 0;        public static final int CENTER = 1;        public static final int RIGHT = 2;        ...                label.setAlignment(Label.CENTER);        ...

    하지만 위의 소스에는 문제가 있다. setAlignment() method의 인자가 int인 것이다. 만약 위에 정의한 0, 1, 2가 아닌 다른 int 값이 들어가도 compile/runtime시 알수가 없다. 그래서 주석을 달게 되는데, 주석이라 함은 정말이지 최후의 수단이라고 봐야 한다.

    실제로 우리가 개발해 놓은 소스에도 이런부분이 있으리라 예상된다. 이 문제를 어떻게 하면 해결할 수 있을까? Java에서 열거형을 한번 만들어 보자.

            //LabelAlignment.java        public class LabelAlignment {            private LabelAlignment() {} //이는 생성자를 private으로 하여 다른데서는 만들지 못하도록 하기위함이다.                        public static final LabelAlignment LEFT = new LabelAlignment():            public static final LabelAlignment CENTER = new LabelAlignment():            public static final LabelAlignment RIGHT = new LabelAlignment():        }                //변형된 Label.java 의 일부..        public synchronized void setAlignment(LabelAlignment alignment){            if( alignment == LabelAlignment.LEFT ){                ...//왼쪽으로 맞추기..            }else if( ...                ...            }        }        ...

    위에서 작성한 소스는 잘 작동한다. 서로 다른 3개의 instance이므로 reference가 달라 '==' 연산도 가능하고, 훌륭하다.

    하지만 한가지 문제가 있다. LabelAlignment가 Serializable한 class에서 serialize되었다 deserialize 된다면?

    LabelAlignment alignment 는 새로운 instance가 되고 serialize전의 reference와 다른 참조 위치를 갖게 되어 '==' 연산은 버그를 발생시킨다. 그럼 이것만 해결하면 되겠는데, 어떻게 refactoring하면 될 것인가? '==' 연산 대신 equals로 변형하면 되겠는데.

            //LabelAlignment.java        public class LabelAlignment {            private int flag;            private LabelAlignment(int flag){                this.flag = flag;            }                         public static final LabelAlignment LEFT = new LabelAlignment(0):            public static final LabelAlignment CENTER = new LabelAlignment(1):            public static final LabelAlignment RIGHT = new LabelAlignment(2):                        public boolean equals(Object obj){                return ((LabelAlignment)obj).flag == this.flag;            }        }                //변형된 Label.java 의 일부..        public synchronized void setAlignment(LabelAlignment alignment){            if( LabelAlignment.LEFT.equals(alignment) ){                ...//왼쪽으로 맞추기..            }else if( ...                ...            }        }        ...

    하하, Serialize까지 잘 작동한다. ^^;

    여기서 Debug를 고려한다면 0, 1, 2 대신 문자열로 "LEFT", "CENTER", "RIGHT"로 한다면 더욱 명확하지 않을까?

    (주의) 위에서처럼 LabelAlignment.LEFT 라고 쓰기 싫어서 상수 interface를 만들어 그걸 implements 하여 그냥 LEFT 라고 쓰는 것을 뿌듯해 하며 쓰는 개발자들이 있다. 물론 Swing의 소스들을 보다보면 SwingConstants라는 interface에 LEFT를 비롯하여 온갖 잡다한 상수를 집어넣어놓고 여기 저기서 implements해서 사용하고 있다. 이런 코딩 스타일은 '내 스타일이야~' 가 아니라 냄새나는 코드이다.

    LEFT라는 것이 구현한 class에 이미 있을 수 있을 수 있을뿐아니라 구현한 모든 클래스에서 LEFT를 보유하여 SwingConstants.LEFT뿐 아니라 Impl.LEFT로도 사용되게 되어 온갖 혼란을 초래하게 된다. 입력량을 줄이기 위해 interface를 implements 해서는 안되지 않을까?

    7.3 Debug write #

    C에서는 다음과 같이 pre-process로 정의하면 DEBUG라는 식별자를 #define하지 않으면 컴파일후 해당 소스의 부분이 삭제된다.

            #ifdef DEBUG            fprintf(stderr, "error...%d\n", error);        #endif /* DEBUG */

    그럼 Java에서는?

    Java에서는 Pre-process가 없지만 다음과 같이 작성했을때 Debug.isDebug 가 final로 선언되어 있으면 compile후 아래 3줄 모두 삭제 된다.(단 Debug.isDebug 가 false 로 초기화 되었다면 제거된다.)

            if( Debug.isDebug ){            System.out.println("error..." + error);        }

    Java는 compile시 byte code 생성시 final은 정적으로 판단하여 미리 정의하기 때문에 위의 3줄은 삭제될 수 있다. if문과 함께 없어지게 되므로 처리 속도에 피해를 주지 않는다. 단, 주의해야 할 점은 Debug.isDebug 값이 변경되면 이 것을 사용하고 있는 측도 모두 함께 다시 compile해야 한다. bytecode를 다시 만들어야 하기 때문이다.

    그런데, 이 소스를 Debug.write()와 같이 static 으로 하여 이 method내에서 판단하게 하면 편리할텐데. 그리고 class별로 ON/OFF 처리를 할 수 있으면 좋을텐데, 어찌 하면 가능할 것인가?

    그럼 먼저 호출한 쪽의 class이름을 찾아보자. 접근은 Exception의 printStackTrace()로 부터 시작되었다. 하지만 이 소스에는 Exception 객체를 new한 시점에 결정되어 있다. 그래서 부모인 Throwable의 생성자를 확인해 보니 fillInStackTrace() 로 되어있는데 이 method는 native method였다.

    API Reference를 보면 Thread class에서는 dumpStackTrace()라는 method가 있었다. 소스를 보니, 그것도 생성시점이었다. 아무래도 예외방면에서 찾는건 무리인듯 했다.

    그래서 class의 호출계층을 나타내는 java.lang.SecurityManager의 getClassContext() method로 접근하였다. sample 소스는 다음과 같다.

            // 1. GetCallerSecurityManager.java        public final class GetCallerSecurityManager extends SecurityManager {            public Class[] getStackTrace(){                return this.getClassContext();               }        }                // 2. GetCallerClass.java        public final class GetCallerClass {            private static GetCallerSecurityManager mgr;                        static{                mgr = new GetCallerSecurityManager();                System.setSecurityManager(mgr);            }                        public static void writeCaller(String str){                Class[] stk = mgr.getStackTrace();                int size = stk.length;                for(int i = 0; i < size; i++){                    System.out.println("stk[" + i + "] = " + stk[i]);                   }                                   String className = stk[2].getName();                                System.out.println("className is " + className + " : " + str);            }        }                // 3. GetCallerClassMain1 : 호출하는 클래스 예제 1        public class GetCallerClassMain1 {            public static void main(String[] args){                GetCallerClass.writeCaller(", real is 1.");            }        }                // 4. GetCallerClassMain1 : 호출하는 클래스 예제 2        public class GetCallerClassMain2 {            public static void main(String[] args){                GetCallerClass.writeCaller(", real is 2.");            }        }

    위의 3번 주석과 4번 주석 부분을 수행하면 다음과 같은 결과가 나온다.

        className is GetCallerClassMain1 : , real is 1.    className is GetCallerClassMain2 : , real is 2.

    정확히 호출한 클래스를 표현하고 있다. 이것을 비교해서 클래스별 ON/OFF를 구현하면 된다.

    8 Java 5.0 Tiger 에 대하여 #

    Tiger에서는 새로운 개념의 적용이 많은 부분 시도 되었다. 이중 가장 기본이 되는 몇가지를 살펴보자.

    8.1 Working with java.util.Arrays #

    Tiger에서는 무엇보다도 Collection class들에 대해 많은 부분 정비하였다. 예를 들면 for/in 구문 지원과 Generic Type member와 Arrays Utility class 등이다. 그럼 Collection에 대한 static method들을 담고 있는 Arrays 에 대해 다음 example로 한눈에 살펴보자.

    package com.jeid.tiger;import java.util.Arrays;import java.util.Comparator;import java.util.List;public class ArraysTester {	private int[] arr;	private String[] strs;	public ArraysTester(int size) {		arr = new int[size];		strs = new String[size];		for (int i = 0; i < size; i++) {			if (i < 10) {				arr[i] = 100 + i;			} else if (i < 20) {				arr[i] = 1000 - i;			} else {				arr[i] = i;			}			strs[i] = "str" + arr[i];		}	}	public int[] getArr() {		return this.arr;	}	public String[] getStrs() {		return this.strs;	}	public static void main(String[] args) {		int size = 50;		ArraysTester tester = new ArraysTester(size);		int[] testerArr = tester.getArr();		int[] cloneArr = tester.getArr().clone();		String[] testerStrs = tester.getStrs();		String[] cloneStrs = tester.getStrs().clone();		// clone test		if (Arrays.equals(cloneArr, testerArr)) {			System.out.println("clonse int array is same.");		} else {			System.out.println("clonse int array is NOT same.");		}		if (Arrays.equals(cloneStrs, testerStrs)) {			System.out.println("clonse String array is same.");		} else {			System.out.println("clonse String array is NOT same.");		}		// 2부터 10까지 값 셋팅		Arrays.fill(cloneArr, 2, 10, new Double(Math.PI).intValue());		testerArr[10] = 98;		testerStrs[10] = "corea";		testerStrs[11] = null;		List<String> listTest = Arrays.asList(testerStrs);		System.out.println("listTest[10] = " + listTest.get(10));		System.out.println("------- unsorted arr -------");		System.out.println("Arrays.toString(int[]) = " + Arrays.toString(testerArr));		System.out.println("Arrays.toString(String[]) = " + Arrays.toString(testerStrs));		Arrays.sort(testerArr);		// Arrays.sort(testerStrs); //NullPointerException in sort method..(null이 없더라도 길이에 대한 크기 체크는 못함)		Arrays.sort(testerStrs, new Comparator<String>() {			public int compare(String s1, String s2) {				if (s1 == null && s2 == null) {					return 0;				} else if (s1 == null && s2 != null) {					return -1;				} else if (s1 != null && s2 == null) {					return 1;				} else if (s1.length() < s2.length()) {					return -1;				} else if (s1.length() > s2.length()) {					return 1;				} else if (s1.length() == s2.length()) {					return 0;				} else {					return s1.compareTo(s2);				}			}		});		System.out.println("------- sorted arr -------");		System.out.println("Arrays.toString(int[]) = " + Arrays.toString(testerArr));		System.out.println("Arrays.toString(String[]) = " + Arrays.toString(testerStrs));				System.out.println("------------------------------------------------");		String[][] mstrs1 = { { "A", "B" }, { "C", "D" } };		String[][] mstrs2 = { { "a", "b" }, { "c", "d" } };		String[][] mstrs3 = { { "A", "B" }, { "C", "D" } };		System.out.println("Arrays.deepToString(mstrs1) = " + Arrays.deepToString(mstrs1));		System.out.println("Arrays.deepToString(mstrs2) = " + Arrays.deepToString(mstrs2));		System.out.println("Arrays.deepToString(mstrs3) = " + Arrays.deepToString(mstrs3));				if( Arrays.deepEquals(mstrs1, mstrs2)) {			System.out.println("mstrs1 is same the mstrs2.");		}else {			System.out.println("mstrs1 is NOT same the mstrs2.");		}				if( Arrays.deepEquals(mstrs1, mstrs3)) {			System.out.println("mstrs1 is same the mstrs3.");		}else {			System.out.println("mstrs1 is NOT same the mstrs3.");		}				System.out.println("mstrs1's hashCode = " + Arrays.deepHashCode(mstrs1));		System.out.println("mstrs2's hashCode = " + Arrays.deepHashCode(mstrs2));		System.out.println("mstrs3's hashCode = " + Arrays.deepHashCode(mstrs3));	}}

    8.2 Using java.util.Queue interface #

    Queue를 이용하여 First In First OutOrdering한 Queue를 구현 가능하다.

    package com.jeid.tiger;import java.util.LinkedList;import java.util.PriorityQueue;import java.util.Queue;public class QueueTester {	public static void main(String[] args) {		System.out.println("---------- testFIFO ----------");		testFIFO();		System.out.println("---------- testOrdering ----------");		testOrdering();	}	private static void testFIFO() {		Queue<String> q = new LinkedList<String>();		q.add("First");		q.add("Second");		q.add("Third");		String str;		while ((str = q.poll()) != null) {			System.out.println(str);		}	}	private static void testOrdering() {		int size = 10;		Queue<Integer> qi = new PriorityQueue<Integer>(size);		Queue<String> qs = new PriorityQueue<String>(size);		for (int i = 0; i < size; i++) {			qi.offer(10 - i);			qs.offer("str" + (10 - i));		}				for (int i = 0; i < size; i++) {			System.out.println("qi[" + i + "] = " + qi.poll() + ", qs[" + i + "] = " + qs.poll());		}	}}

    8.3 java.lang.StringBuilder 사용하기 #

    StringBuffer가 synchronize하지 않은 method들로 구성된 듯한 StringBuilder를 사용하므로 성능 향상을 도모할수 있다. 사용법은 StringBuffer와 동일하다.

    package com.jeid.tiger;import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class StringBuilderTester {	public static void main(String[] args) {		List<String> list = new ArrayList<String>();		list.add("str1");		list.add("str2");		list.add("str3");		String ret = appendItems(list);		System.out.println("ret = " + ret);	}	private static String appendItems(List<String> list) {		StringBuilder sb = new StringBuilder();		for (Iterator<String> iter = list.iterator(); iter.hasNext();) {			sb.append(iter.next()).append(" ");		}		return sb.toString();	}}

    8.4 Using Type-Safe Lists #

    Collection에 type을 명시하여 type-safe 하게 처리 가능. 아래에서 type을 명시하지 않을 경우 compile error가 남을 보여준다. tip으로 Number를 이용하여 byte, short, int, long, double, float 동시 사용하는 부분 참조.

    package com.jeid.tiger;import java.util.Iterator;import java.util.LinkedList;import java.util.List;public class ListTester {	public static void main(String[] args) {		List<String> list = new LinkedList<String>();		list.add("str1");		list.add("str2");		list.add(new Integer(123));  // <-- String이 아니므로 compile error!!				//Iterator에 String type을 명시하므로 정삭작동됨.		for (Iterator<String> iter = list.iterator(); iter.hasNext();) {			String str = iter.next();			System.out.println("srt = " + str);		}				//Iterator에 String type을 명시하지 않았으므로 아래 A 부분에서 compile 오류 발생!!		for (Iterator iter = list.iterator(); iter.hasNext();) {			String str = iter.next(); //A			System.out.println("srt = " + str);		}				//byte, short, int, long, double, float 동시 사용		List<Number> lstNum = new LinkedList<Number>();		lstNum.add(1);		lstNum.add(1.2);		for (Iterator<Number> iter = lstNum.iterator(); iter.hasNext();) {			Number num = iter.next();			System.out.println("num = " + num);		}	}}

    8.5 Writing Generic Types #

    class 나 interface keyword에 type을 명시하여 동일 타입 명시 가능. 주의 할 점은 any type은 static 일 수 없다.(동적으로 type이 정해지므로)

    class AnyTypeList<T> {//class AnyTypeList<T extends Number> {  // <-- 이는 Number를 상속한 type은 허용하겠다는 의미.	private List<T> list;	//private static List<T> list;  // <-- 이는 정적이므로 compile error 발생!!! 	public AnyTypeList(){		list = new LinkedList<T>();	}		public boolean isEmpty(){		return list == null || list.size() == 0;	}		public void add(T t){		list.add(t);	}		public T grap(){		if (!isEmpty() ) {			return list.get(0);		} else {			return null;		}	}}

    8.6 새로운 static final enum #

    예제를 통해 알아보자.

    package com.jeid.tiger;import com.jeid.BaseObject;import com.jeid.MyLevel;public class EnumTester extends BaseObject {	private static long start = System.currentTimeMillis();		public static void main(String[] args) {		try {			test();			enum1();		} catch (Exception e) {			e.printStackTrace();		}		printEllapseTime();	}		private static void test() throws Exception {		byte[] b = new byte[0];		System.out.println(b.length);	}	private static void enum1() {		//enum TestEnum { A, B };  //enum cannot be local!!!				for(MyVO.TestEnum te: MyVO.TestEnum.values()){			System.out.println("Allow TestEnum value : " + te);		}		System.out.println("---------------------------------------");				MyVO vo = new MyVO();		vo.setName("enum1");		vo.setLevel(MyLevel.A);		System.out.println(vo);		System.out.println("isA = " + vo.isA() + ", isGradeA = " + vo.isLevelA()+ ", isValueOfA = " + vo.isValueOfA());		System.out.println("getLevelInKorean = " + vo.getLevelInKorean());	}	private static void printEllapseTime() {		System.out.println("==> ellapseTime is " + (System.currentTimeMillis() - start) + " ms.");	}}package com.jeid.tiger;import com.jeid.BaseObject;import com.jeid.MyLevel;public class MyVO extends BaseObject {	enum TestEnum {		A, B	}; // this is same public static final	private int id;	private String name;	private MyLevel grade;	// private List<T> list;	public MyLevel getLevel() {		return grade;	}	public void setLevel(MyLevel grade) {		this.grade = grade;	}	public boolean isA() {		return "A".equals(this.grade);	}	public boolean isValueOfA() {		return MyLevel.valueOf("A").equals(grade);	}	public boolean isLevelA() {		return MyLevel.A.equals(this.grade);	}	//A,B,C..대신 0,1,2... 도 동일함.	public String getLevelInKorean() {		switch(this.grade){		case A:			return "수";		case B:			return "우";		case C:			return "미";		case D:			return "양";		case E:			return "가";		default:			return "없음";		}	}	public int getId() {		return id;	}	public void setId(int id) {		this.id = id;	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}}

    8.7 Using java.util.EnumMap #

    java.util.Map과 동일하나 key가 enum type이어 한다. 예제로 살펴보자.

    package com.jeid.tiger;import java.util.EnumMap;public class EnumMapTester {	private enum MyEnum {		A, B, C	}; // this is same the static final..	public static void main(String[] args) {		MyEnum[] enums = MyEnum.values();		System.out.println("MyEnum is " + enums[0] + ", " + enums[1] + ", " + enums[2]);		EnumMap<MyEnum, String> em = new EnumMap<MyEnum, String>(MyEnum.class);		em.put(MyEnum.A, "수");		em.put(MyEnum.B, "우");		em.put(MyEnum.C, "미");		em.put(MyEnum.B, "가"); //key 중복은 HashMap과 동일하게 overwrite임.		for (MyEnum myEnum : MyEnum.values()) {			System.out.println(myEnum + " => " + em.get(myEnum));		}	}}

    8.8 Using java.util.EnumSet #

    java.util.Set과 동일하나 value가 enum type이어 한다. 예제로 살펴보자.

    package com.jeid.tiger;import java.util.EnumSet;public class EnumSetTester {	private enum MyEnum {		A, B, C, a, b, c	}; // this is same the static final..	public static void main(String[] args) {		MyEnum[] enums = MyEnum.values();		System.out.println("MyEnum is " + enums[0] + ", " + enums[1] + ", " + enums[2]);		EnumSet<MyEnum> es1 = EnumSet.of(MyEnum.A, MyEnum.B, MyEnum.C);		EnumSet<MyEnum> es2 = EnumSet.of(MyEnum.a, MyEnum.b, MyEnum.c);		EnumSet<MyEnum> es3 = EnumSet.range(MyEnum.a, MyEnum.c);		if (es2.equals(es3)) {			System.out.println("e2 is same e3.");		}		for (MyEnum myEnum : MyEnum.values()) {			System.out.println(myEnum + " contains => " + es1.contains(myEnum));		}	}}

    8.9 Convert Primitives to Wrapper Types #

    int, short, char, long, double등 primitive와 이들의 Object Wrapper 인 Integer, Shrt, Char등 간의 converting에 있어 자동으로 처리해주는 boxing과 unboxing이 지원 됨에 따라 type에 대한 유연한 처리가 가능해졌다. 예제로 살펴보자.

    package com.jeid.tiger;public class AutoBoxingTester {	public static void main(String[] args) {		int i = 0;		Integer ii = i; // boxing. JDK 1.4에서는 incompatible type error가 발생 했었으나 Tiger에서는 괜찮다.		int j = ii; // unboxing		for (ii = 0; ii < 5; ii++) { // Integer인데도 ++ 연산자 지원.		}		i = 129;		ii = 129;		if (ii == i) {			System.out.println("i is same ii.");		}		// -128 ~ 127 사이의 수는 unboxing이 되어 == 연산이 허용되지만,		// 그 범위 외의 경우 Integer로 boxing된 상태므로 equals를 이용해야함.		// 이는 버그가 발생했을 경우 찾기 쉽지 않은 단점도 내포하고 있다.!!		checkIntegerSame(127, 127); // same		checkIntegerSame(128, 128); // Not same		checkIntegerEquals(128, 128); // equals		checkIntegerSame(-128, -128); // same		checkIntegerSame(-129, -129); // Not same		checkIntegerEquals(-129, -129); // equals				System.out.println("--------------------------------------------");		Boolean arriving = false;		Boolean late = true;		String ret = arriving ? (late ? "도착했지만 늦었네요." : "제시간에 잘 도착했군요.") : 								(late ? "도착도 못하고 늦었군요." : "도착은 못했지만 늦진 않았군요.");		System.out.println(ret);				StringBuilder sb = new StringBuilder();		sb.append("appended String");		String str = "just String";		boolean mutable = true;		CharSequence chSeq = mutable ? sb : str;		System.out.println(chSeq);	}	private static void checkIntegerSame(Integer ii, Integer jj) {		if (ii == jj) {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is same ii.");		} else {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is NOT same ii!!");		}	}	private static void checkIntegerEquals(Integer ii, Integer jj) {		if (ii.equals(jj)) {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is equals ii.");		} else {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is NOT equals ii!!");		}	}}

    8.10 Method Overload resolution in AutoBoxing #

    int가 127을 초과할 경우 boxing이 이루어 질듯 하지만, method overload에 있어서는 boxing이 이루어 지지 않아 JDK1.4와 동일한 결과를 얻는다. 예제로 살펴보자.

    package com.jeid.tiger;public class OverloadTester {	public static void main(String[] args) {		double d = 10;		Integer ii = new Integer(10);		doSomething(10);		doSomething(1000);		doSomething(ii);		doSomething(d);	}	private static void doSomething(Integer ii) {		System.out.println("This is doSomething(Integer)");	}	private static void doSomething(double d) {		System.out.println("This is doSomething(double)");	}}

    8.11 가변적인 argument 개수 ... #

    인수가 가변적일 경우 인수의 개수가 없는것 부터 다수개까지 모두 지원. 예제로 살펴보자.

    package com.jeid.tiger;public class VarArgsTester {	public static void main(String[] args) {		setNumbers(1, 2);		setNumbers(1, 2, 3, 4);		setNumbers(1);		// setNumbers(); //해당 되는 method가 없어 compile error!!		System.out.println("==============================================");		setNumbers2(1, 2, 3, 4);		setNumbers2(1);		setNumbers2();	}	// this is same setNumbers(int first, int[] others)	private static void setNumbers(int first, int... others) {		System.out.println("-----------setNumbers()----------- : " + first);		for (int i : others) {			System.out.println("i = " + i);		}	}	// this is same setNumbers(int[] others)	private static void setNumbers2(int... others) {		System.out.println("-----------setNumbers2()----------- : "				+ (others != null && others.length > 0 ? others[0] : "null"));		for (int i : others) {			System.out.println("i = " + i);		}	}}

    8.12 The Three Standard Annotation #

    @Override - sign the override from superclass.

        //정상적인 사용    @Override    public int hashCode(){        return toString().hashCode();    }        //스펠링이 틀려 compile error!!    @Override    public int hasCode(){   //misspelled => method does not override a method from its superclass error!!        return toString().hashCode();    }

    @Deprecated deprecated 주석과 동일하나 부모의 method가 deprecated되면 자식의 method를 사용해도 deprecated로 나온다.

    package com.jeid.tiger;public class AnnotationDeprecateTester {	public static void main(String[] args){		DeprecatedClass dep = new DeprecatedTester();		dep.doSomething(10);    //deprecated	}}class DeprecatedClass {	@Deprecated	public void doSomething(int ii){    //deprecated		System.out.println("This is DeprecatedClass's doSomething(int)");	}		public void doSomethingElse(int ii){		System.out.println("This is DeprecatedClass's doSomethingElse(int)");	}}class DeprecatedTester extends DeprecatedClass {	@Override	public void doSomething(int ii){		System.out.println("This is DeprecatedTester's doSomething(int)");	}}

    @SuppressWarnings SuppressWarnings에 인자는 String[] type으로 여러개를 배열형태로 쓸수 있다.

    package com.jeid.tiger;import java.util.ArrayList;import java.util.List;public class AnnotationSuppressWarningsTester {	@SuppressWarnings({"unchecked", "fallthrough"} )	private static void test1(){		List list = new ArrayList();		list.add("aaaaaa");	}		@SuppressWarnings("unchecked")	private static void test2(){		List list = new ArrayList();		list.add("aaaaaa");	}		//warning이 없는 소스.	private static void test3(){		List<String> list = new ArrayList<String>();		list.add("aaaaaa");	}}

    8.13 Creating Custom Annotation Types #

    나만의 annotation을 정의할 수 있는데 키워드는 @interface이 각 method정의가 member라고 보면 된다. 간단한 예를 보면 다음과 같다.

    package com.jeid.tiger;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Documented@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {	String columnName();	String methodName() default "";}//사용하는 쪽..public class AnnotationTester {	@MyAnnotation(columnName = "test", methodName = "setTest")	private String test;	@MyAnnotation(columnName = "grpid")	public String grpid;    ....}//위의 test 멤버의 경우 다음과 같이 접근 가능하다.Field testField = cls.getDeclaredField("test");if (testField.isAnnotationPresent(MyAnnotation.class)) {    Annotatioin anno = testField.getAnnotation(MyAnnotation.class);    System.out.println(anno.columnName() + ", method = " + anno.methodName());}

    9 The for/in Statement #

    9.1 for/in 의 자주 사용되는 형태 #

    for/in은 무엇보다 다양한 유형의 예제를 보는것이 제일 빠를것이다. 형태별 사용 예제를 살펴보면 다음과 같다.

    //1. 가장 단순한 형태인 배열(array)String[] strs = { "aaa", "bbb", "ccc" };for (String str : strs) {    System.out.println(str);}//2. List by using IteratorList<Number> lstNum = new LinkedList<Number>();lstNum.add(1);lstNum.add(1.2);for (Iterator<Number> iter = lstNum.iterator(); iter.hasNext();) {	Number num = iter.next();	System.out.println("num = " + num);}//3. List를 바로 사용List<String> lst = new LinkedList<String>();lst.add("aaaaa");lst.add("bbbbb");lst.add("ccccc");lst.add("ddddd");for (String str : lst) {	System.out.println("str = " + str);}// 4. List of ListList[] lists = { lst, lst };for (List<String> l : lists) {	for (String str : l) {		System.out.println("str = " + str);	}}

    10 Static Import #

    10.1 static member/method import #

    Tiger에서는 다른 클래스의 member와 method를 import 할수 있다. 단, static 일 경우만 가능하다.

    //예를 들어 System.out.println() 이라는 것을 사용하기 위해서는 다음의 import 문이 필요하다.import java.lang.System;   //물론 java.lang 이기에 import 문이 필요없지만 예를 들자면 그렇다는 것이다.&^^//허나, Tiger에서는 다음과 같이 사용할수 있다.import static java.lang.System.out;...out.println(...);// method를 import 한다면..import static java.lang.System.out.println;...println(...);

    Posted by 1010
    90.개발관련문서2008. 8. 12. 11:45
    반응형
    Welcome  

    Welcome to Google Doctype. Written by web developers, for web developers.

    What do you want to learn today?

    Read HOWTO articles on

    Dive into DOM objects, including

    Style your pages with CSS

    Do you need help with an HTML element? We have an HTML reference from <a> to <xmp>.

    Posted by 1010