[CLEAN CODE] 점진적인 개선(Successive Refinement)

📢 Args 구현

Args Implementation

Args 사용

package com.objectmentor.utilities.args;

public class Application {
    public static void main(String[] args) {
        try {
            Args arg = new Args("l,p#,d*", args);
            boolean logging = arg.getBoolean('l');
            int port = arg.getInt('p');
            String directory = arg.getString('d');
            executeApplication(logging, port, directory);
        } catch (ArgsException e) {
            System.out.printf("Argument error: %s\n", e.errorMessage());
        }
    }
}

Args 구현

package com.objectmentor.utilities.args;

import java.util.*;
import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;

public class Args {
    private Map<Character, ArgumentMarshaler> marshalers;
    private Set<Character> argsFound;
    private ListIterator<String> currentArgument;

    public Args(String schema, String[] args) throws ArgsException {
        marshalers = new HashMap<Character, ArgumentMarshaler>();
        argsFound = new HashSet<Character>();

        parseSchema(schema);
        parseArgumentStrings(Arrays.asList(args));
    }

    private void parseSchema(String schema) throws ArgsException {
        for (String element : schema.split(","))
            if (element.length() > 0)
                parseSchemaElement(element.trim());
    }

    private void parseSchemaElement(String element) throws ArgsException {
        char elementId = element.charAt(0);
        String elementTail = element.substring(1);
        validateSchemaElementId(elementId);
        if (elementTail.length() == 0)
            marshalers.put(elementId, new BooleanArgumentMarshaler());
        else if (elementTail.equals("*"))
            marshalers.put(elementId, new StringArgumentMarshaler());
        else if (elementTail.equals("#"))
            marshalers.put(elementId, new IntegerArgumentMarshaler());
        else if (elementTail.equals("##"))
            marshalers.put(elementId, new DoubleArgumentMarshaler());
        else if (elementTail.equals("[*]"))
            marshalers.put(elementId, new StringArrayArgumentMarshaler());
        else
            throw new ArgsException(INVALID_ARGUMENT_FORMAT, elementId, elementTail);
    }

    private void validateSchemaElementId(char elementId) throws ArgsException {
        if (!Character.isLetter(elementId))
            throw new ArgsException(INVALID_ARGUMENT_NAME, elementId, null);
    }

    private void parseArgumentStrings(List<String> argsList) throws ArgsException {
        for (currentArgument = argsList.listIterator(); currentArgument.hasNext(); ) {
            String argString = currentArgument.next();
            if (argString.startsWith("-")) {
                parseArgumentCharacters(argString.substring(1));
            } else {
                currentArgument.previous();
                break;
            }
        }
    }

    private void parseArgumentCharacters(String argChars) throws ArgsException {
        for (int i = 0; i < argChars.length(); i++)
            parseArgumentCharacter(argChars.charAt(i));
    }

    private void parseArgumentCharacter(char argChar) throws ArgsException {
        ArgumentMarshaler m = marshalers.get(argChar);
        if (m == null) {
            throw new ArgsException(UNEXPECTED_ARGUMENT, argChar, null);
        } else {
            argsFound.add(argChar);
            try {
                m.set(currentArgument);
            } catch (ArgsException e) {
                e.setErrorArgumentId(argChar);
                throw e;
            }
        }
    }

    public boolean has(char arg) {
        return argsFound.contains(arg);
    }

    public int nextArgument() {
        return currentArgument.nextIndex();
    }

    public boolean getBoolean(char arg) {
        return BooleanArgumentMarshaler.getValue(marshalers.get(arg));
    }

    public String getString(char arg) {
        return StringArgumentMarshaler.getValue(marshalers.get(arg));
    }

    public int getInt(char arg) {
        return IntegerArgumentMarshaler.getValue(marshalers.get(arg));
    }

    public double getDouble(char arg) {
        return DoubleArgumentMarshaler.getValue(marshalers.get(arg));
    }

    public String[] getStringArray(char arg) {
        return StringArrayArgumentMarshaler.getValue(marshalers.get(arg));
    }
}

위에서 아래로 코드가 읽힌다는 사실에 주목하자.
코드를 주의깊게 읽었다면 ArgumentMarshaler 인터페이스가 무엇이며 파생 클래스가 무슨 기능을 하는지 이해하리라.

이름을 붙인 방법, 함수 크기, 코드 형식에 각별히 주목하자.

어떻게 짰느냐고?

How Did I Do This?

처음부터 우아한 프로그램을 뚝딱 내놓으리라 기대하지 않는다.
프로그래밍은 과학보다 공예(Craft)에 가깝다.
깨끗한 코드를 짜려면 먼저 지저분한 코드를 짠 뒤에 정리해야 한다.
대다수는 무조건 돌아가는 프로그램을 목표로 잡지만 '돌아가는'프로그램에서 멈춘다면 자살 행위나 다름 없다.

📢 Args: 1차 초안

Args: The Rough Draft

처음 짰던 Args 클래스는 코드는 '돌아가지만' 엉망이었다.
처음부터 지저분한 코드를 짜려는 생각은 없었지만 어느 순간 프로그램은 내 손을 벗어났다.
코드는 조금씩 엉망이 되어갔다.

그래서 멈췄다

So I Stopped

기능을 더 이상 추가하지 않기로 결정하고 리팩터링을 시작했다.

  1. 인수 유형에 해당하는 HashMap을 선택하기 위해 스키마 요소의 구문 분석
  2. 명령행 인수에서 인수 유형을 분석해 진짜 유형으로 변환
  3. getXXX 메서드를 구현해 호출자에게 진짜 유형 반환

점진적으로 개선하다

On Incrementalism

개선이라는 이름으로 구조를 크게 뒤집는 행위는 프로그램을 망치는 방법이다.
테스트 주도 개발이라는 기법을 사용해서 변경 전후에 시스템이 똑같이 돌아간다는 사실을 확인해야 한다.

📢 String 인수

String Arguments

소프으퉤어 설계는 분할만 잘해도 품질이 크게 높아진다.
적절한 장소를 만들어 코드만 분리해도 설계가 좋아진다.
관심사를 분리하면 코드를 이해하고 보수하기 훨씬 더 쉬워진다.

📢 결론

Conclusion

그저 돌아가는 코드만으로는 부족하다.
나쁜 일정은 다시 짜면 되고, 나쁜 요구사항은 다시 정의하면 되고, 나쁜 팀 관계는 다시 바로 잡으면 된다.
하지만, 나쁜 코드는 썩어 문드러져서 팀의 발목을 붙잡는다.
코드를 언제나 최대한 깔끔하고 단순하게 정리하자.

📖 느낀점
'아침에 엉망으로 만든 코드를 오후에 정리하기는 어렵지 않다.
더욱이 5분 전에 엉망으로 만든 코드는 지금 당장 정리하기 아주 쉽다'는 말이 기억에 남는다.
내가 짜는 코드는 돌아가는 코드이지, 깨끗한 코드라고 장담할 수 없다.
이 책을 읽으면서 계속 드는 생각은 하나인 것 같다.
'나는 깨끗한 코드를 짜고 있지 않다.'
그래서 더 열심히 하고 싶다. 더 깨끗한 코드를 짜기 위해서 어떻게 해야하는지 공부하고 싶다.
더 나아가고 싶다. 요즘 그런 생각을 자주 하게 되는 것 같다.

좋은 웹페이지 즐겨찾기