mir.pe (일반/밝은 화면)
최근 수정 시각 : 2024-08-26 20:10:28

하드코딩

1. 개요2. 장점3. 단점4. 예시
4.1. 하드코딩4.2. 상수 테이블 사용4.3. 외부 리소스 파일 사용4.4. 애플리케이션 프레임워크 사용

1. 개요

데이터를 소스 코드 내부에 리터럴의 형태로 직접 입력하는 것. 기술적으로는 데이터가 실행 바이너리(exe 파일 등)에 합쳐져 있는 상태를 말한다. 반대말은 소프트코딩 또는 로딩.

프로그램의 소스 코드에 데이터를 직접 입력해서 저장한 경우, 즉 모든 '상수'는 하드코딩이다. '변수'의 초기값이나 기본값도 하드코딩이다. 기본값 자체를 외부 리소스 파일로부터 읽어서 초기화하는 경우도 있지만 그 '리소스' 파일의 로딩은 실패 확률이 존재하기 때문에 로딩 전까지는 null, 0, nil 등의 값이 하드코딩되어있다.

주로 파일 경로, URL 또는 IP 주소, 비밀번호, 화면에 출력될 문자열 등이 대상이 된다.

2. 장점


코드의 목적이 매우 직관적이라 가독성도 괜찮은 편이고 컴퓨터의 입장에서는 타입 체크나 유효성 검사 등이 빠지고 별도의 로딩 절차가 없어서 속도가 올라가므로 정말 절대로 변경되지 않을 것이라고 자신할 수 있는 작은 부분에 한정적으로 사용하면 좋다.[1] 또는 데이터를 수정하면 로직도 수정이 불가피해서 재컴파일을 피할 수 없는 경우에도 하드코딩이 적절하다. 예를 들어 SQL 같은 경우에는 변수를 제외한 구문 전체를 하드코딩하는데[2], SQL 구문을 변경하면 SQL이 수행된 뒤의 처리 로직 전체도 바꿔줘야 해서 재컴파일이 불가피해지기 때문이다.[3]

3. 단점

4. 예시

4.1. 하드코딩

HelloWorld.java
#!syntax java
public class HelloWorld {
  public static void main(String args[]) {
    System.out.println("Hello World!");
  }
}


다섯 줄만으로 간단하고 깔끔하게 헬로 월드를 출력한다. 하지만 출력 문자열을 "Hi World!" 로 바꾸려 해도 컴파일을 다시 해야 한다.

4.2. 상수 테이블 사용

Resource.java
#!syntax java
public final class Resource {
  public static final String greeting = "Hello World!";
}


HelloWorld.java
#!syntax java
public class HelloWorld {
  public static void main(String args[]) {
    System.out.println(Resource.greeting);
  }
}


프로그래머에 따라서는 아직까지는 하드코딩으로 간주하는 경우도 있다. 컴파일을 다시 해야 하는 건 똑같기 때문이다. 하지만 HelloWorld.java는 다시 컴파일하지 않고 Resource.java 파일만 컴파일하면 되므로 수정시 컴파일 시간은 줄어든다.

만약 컴파일 언어가 아닌 스크립트 언어라면( Python 등) 따로 컴파일을 하지 않기 때문에 상수 테이블 방식으로 구현하는 게 가장 깔끔한 경우가 많다. 스크립트 언어 자체가 일종의 DSL(Domain Specific Language 도메인 특화 언어) 성질을 갖기 때문이다.

이 경우 문제점은 상수 테이블 안에 값 말고 메소드 같은 걸 붙인다거나 하면 결국 하드코딩과 다를 게 없어진다는 사실이다. 상수 테이블로 사용할 소스 코드에는 상수값만 있어야 한다. 상수 테이블을 상속한다든지, 객체를 만든다든지(new 연산자 등) 하면 안 된다. 상수 테이블은 프로그램 전체에서 유일(static)해야 하고 불변해야 하며(const, final) 투명(public)해야 한다. 셋 중 하나라도 어기면 그건 상수 테이블이 아니라 데이터 '객체'가 되며, 소스 코드의 일부분이 되고 결국 하드코딩이 된다.

4.3. 외부 리소스 파일 사용

config.properties
greeting=Hello World!


HelloWorld.java
#!syntax java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class HelloWorld {
  public static void main(String args[]) {
    Properties prop = new Properties();

    try(InputStream input = new FileInputStream("config.properties")){
      prop.load(input);

      System.out.println(prop.getProperty("greeting"));
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
}


Java 언어에서 컴파일 없이 설정 파일을 불러오는 방법 중 가장 간단한 방법. 이 방법은 config.properties 파일이 단순 텍스트 파일이라 재컴파일이 필요없다. 하지만 코드의 길이가 대폭 늘어나고 직관성이 떨어진다. 주석을 달지 않으면 도대체 이게 뭐하는 코드인지 알아볼 수 없을 정도가 된다.

더 복잡한 자료구조를 처리하려면 JSON이나 XML을 사용해서 처리하거나 아예 따로 DSL을 작성해야 한다. 위의 코드보다 코드 길이는 더더욱 늘어난다. 특히 DSL 레벨까지 가면 그 DSL을 처리하는 코드는 사실상의 컴파일러다. 그렇게 되면 그 DSL이 스스로의 소스 코드가 되면서 또다시 하드코딩이 되어버린다. 도대체 어쩌라고

게다가 불러올 파일 자체가 크고 아름답다면 그만큼 처리 지연이 생긴다. 일장일단이 있는 셈.

4.4. 애플리케이션 프레임워크 사용

더 이상의 자세한 설명은 생략한다.

Java에서는 스프링 프레임워크를 사용하는데, 제어 반전 컨테이너와 설정 xml 파일을 사용해서 의존성을 주입하고...라고밖엔 말할 수 없다. 코드의 줄 수는 다 합치면 200여 줄을 우습게 넘긴다.

애플리케이션 프레임워크를 고려하는 시점에 와서는 프로그램이 뭘 출력할 것인가 정도에서 그치지 않고, 언제 어떤 방식으로 무엇을 출력할 것인가 혹은 출력하지 않을 것인가 정도로 고민의 범위가 커진 뒤이기 때문에, 하드코딩으로는 도저히 관리가 안 되어 200줄이 넘어가는 코드량을 감내하는 것이다. 그게 아니라면, 그저 삽질. 도구는 적절한 도구를 적절한 장소에 알맞게 써야 하는 법이다.


[1] 소프트웨어에서 이런 경우가 어딨냐 라고 할 수도 있지만, 중장비 프로그래밍에서는 한번 정한 부분은 안바꾸는 경우가 꽤 많다. 조금이라도 바꾸려다가는 부품 외주업체한테 다 수정요청 돌려야 하는 수도 있기 때문에. [2] 이렇게 하는 이유는 SQL injection 공격을 방어하는 가장 확실한 방법이기 때문. [3] 다만 이런 용도로 쓰라고 아예 SQL 쪽에서 프로시저(Procedure)라는 기능이 따로 마련되어 있다.