10장: 변수 사용 시 고려할 사항

10장: 변수 사용 시 고려할 사항

  • 구현 단계에서는 요구사항과 아키텍처가 완전히 지정하지 않은 작은 세부를 채움
  • 이 장은 그중 변수 사용이라는 낮은 수준의 구현 문제를 다룸
  • 여기서 변수는 정수나 배열 같은 내장 데이터형뿐 아니라 객체도 포함함
  • 데이터형은 주로 내장 데이터형을, 데이터는 객체와 내장 데이터 모두를 가리킴

10.1 데이터 활용 능력

  • 효과적인 데이터를 만들기 위한 첫 단계는 어떤 종류의 데이터를 만들 수 있는지 아는 것임
  • 다양한 데이터형에 익숙한 것은 프로그래머의 기본 도구함에 가까움
  • 추상 데이터형, 배열, 비트맵, 불린, B-tree, 컨테이너, 열거형, 힙, 인덱스, 링크드 리스트, 상수, 포인터, 참조 무결성, 스택, 문자열, 트리, typedef, 공용체, variant 같은 개념을 알고 있는지 점검함
  • 목록에는 실제 데이터형이 아닌 항목도 섞여 있음. 모르는 것을 안다고 넘기지 않는 지적 정직성도 중요함

10.2 변수 선언을 쉽게 만들기

  • 변수 선언은 작은 일이지만 자주 하므로 좋은 습관이 장기적으로 시간을 절약함

암시적 선언

  • 암시적 선언은 매우 위험한 기능임
  • 암시적 선언이 있는 언어에서는 가능한 한 기능을 끔

10.3 변수 초기화 지침

  • 잘못된 데이터 초기화는 오류의 큰 원천임
  • 초기화 문제는 변수에 예상하지 못한 값이 들어 있을 때 발생함
  • 변수에 값이 한 번도 할당되지 않았거나, 값이 오래되었거나, 일부만 초기화되었을 수 있음
  • 객체 멤버 일부만 초기화하거나, 포인터가 가리키는 메모리를 잘못 다루는 경우도 포함됨

초기화 문제를 피하는 방법

  • 변수를 선언할 때 바로 초기화함
  • 선언 시 초기화할 수 없다면 처음 쓰는 곳 가까이에서 초기화함
  • 가능한 언어에서는 변수 선언과 정의를 첫 사용 지점 가까이에 둠
  • 바뀌면 안 되는 값에는 final이나 const를 사용함
  • 카운터와 누산기는 사용할 때마다 제대로 초기화·재초기화되는지 특히 확인함
  • 클래스 멤버 데이터는 생성자에서 초기화하고, 생성자에서 할당한 메모리는 소멸자에서 해제함
  • 루프나 반복 호출 때문에 재초기화가 필요한지 확인하고, 반복되는 코드 안에 초기화를 둠
  • 이름 있는 상수는 한 번만 초기화하고, 진짜 변수는 실행 코드에서 사용 지점 가까이에 초기화함
  • 컴파일러의 자동 초기화 옵션을 쓴다면 그 의존성을 문서화함
  • 컴파일러 경고를 적극 활용함
  • 입력 매개변수는 값을 사용하기 전에 유효성을 확인함
  • 포인터가 있는 환경에서는 메모리 접근 검사 도구를 사용함
  • 프로그램 시작 시 작업 메모리를 알려진 값으로 채우면 초기화 결함을 더 빨리 드러낼 수 있음

10.4 범위

  • 범위는 변수가 프로그램 어디까지 알려져 있고 참조될 수 있는지를 뜻함
  • 작은 범위의 변수는 작은 루프나 한 루틴 안에서만 보임
  • 큰 범위의 변수는 많은 루틴이나 전체 프로그램에서 보임
  • 언어에 따라 블록, 루틴, 클래스, 패키지, 네임스페이스, 전체 프로그램 등 다양한 범위를 제공함

변수 참조를 지역화하기

  • 변수 참조 사이의 코드는 취약한 창임
  • 그 사이에 새 코드가 들어와 변수를 의도치 않게 바꾸거나, 읽는 사람이 변수 값을 잊을 수 있음
  • 변수 참조는 가능하면 가까이 모음
  • span은 한 변수의 참조와 다음 참조 사이에 있는 코드 길이를 뜻함
  • span이 짧을수록 읽는 사람이 한 번에 이해해야 할 코드가 줄어듦

변수 생존 시간을 짧게 유지하기

  • live time은 변수가 처음 참조된 문장부터 마지막으로 참조된 문장까지의 길이임
  • 참조 횟수가 많아도 첫 참조와 마지막 참조가 멀면 생존 시간이 김
  • 생존 시간이 짧으면 의도치 않은 변경 가능성이 줄어듦
  • 초기화 오류 가능성이 줄고, 읽어야 할 코드 범위도 줄어듦
  • 관련 코드가 가까워지므로 큰 루틴을 작은 루틴으로 나누기도 쉬워짐
  • 전역 변수는 span과 live time이 매우 커지므로 피해야 할 이유가 하나 더 생김

범위를 최소화하는 지침

  • 루프에서 쓰는 변수는 루틴 시작이 아니라 루프 바로 앞에서 초기화함
  • 값은 실제로 쓰기 직전에 할당함
  • 같은 변수를 다루는 관련 문장을 함께 묶음
  • 관련 문장 묶음은 별도 루틴으로 분리함
  • 가장 제한적인 가시성에서 시작하고, 필요할 때만 범위를 넓힘
  • 지역 루프, 개별 루틴, 클래스의 비공개 멤버, 보호 멤버, 패키지, 전역 순서로 가능한 작은 범위를 우선함
  • 편의성 때문에 범위를 넓히면 작성은 쉬울 수 있어도 읽기, 디버깅, 수정은 어려워짐
  • 공유가 필요하면 노출된 전역 데이터보다 접근 루틴을 고려함

10.5 지속성

  • 지속성은 데이터가 살아 있는 기간임
  • 블록이나 루틴 동안만 지속되는 변수, 명시적으로 해제되거나 GC될 때까지 지속되는 변수, 프로그램 전체 동안 지속되는 변수, 실행 사이에도 저장되는 데이터가 있음
  • 문제는 변수가 실제보다 더 오래 지속된다고 가정할 때 생김
  • 값이 남아 있는 것처럼 보여 오류가 숨어 있을 수도 있음

지속성 오류를 피하는 방법

  • 중요한 변수에는 디버그 코드나 어설션으로 합리적인 값인지 확인함
  • 사용이 끝난 변수에는 일부러 말이 안 되는 값을 넣어 잘못된 재사용을 드러냄
  • 데이터가 지속되지 않는다고 가정하고 작성함
  • 언어가 보장하는 경우가 아니라면, 루틴을 다시 들어올 때 이전 값이 남아 있다고 기대하지 않음
  • 데이터를 사용하기 직전에 선언하고 초기화하는 습관을 둠

10.6 바인딩 시점

  • 바인딩 시점은 변수와 값이 연결되는 시점임
  • 코드 작성 시점, 컴파일 시점, 로드 시점, 객체 생성 시점, 실제 사용 직전 등 여러 시점이 있음
  • 일반적으로 바인딩이 늦을수록 유연성은 커짐
  • 하지만 늦은 바인딩을 지원하려면 코드가 복잡해지고 오류 가능성도 늘어남
  • 매직 넘버처럼 코드 작성 시점에 값을 박는 방식은 유연성이 낮음
  • 이름 있는 상수는 컴파일 시점 바인딩으로, 가독성과 변경 용이성을 높이면서 런타임 비용을 들이지 않음
  • 설정 파일이나 레지스트리에서 읽는 값은 로드 시점이나 실행 시점 바인딩으로, 프로그램 변경 없이 값을 바꿀 수 있음
  • 필요한 유연성만 제공하고 요구사항을 넘는 유연성과 복잡성은 추가하지 않음

10.7 데이터형과 제어 구조 사이의 관계

  • 데이터형과 제어 구조는 서로 잘 맞물림
  • 순차 데이터는 순차 명령문으로 이어짐
  • 선택 데이터는 ifcase 같은 조건문으로 이어짐
  • 반복 데이터는 for, while, repeat 같은 반복문으로 이어짐
  • 실제 데이터는 순차, 선택, 반복이 조합된 형태일 수 있음
  • 데이터 구조를 이해하면 자연스러운 제어 흐름도 더 쉽게 잡힘

10.8 각 변수를 정확히 한 가지 목적으로 사용

  • 변수 하나를 여러 목적으로 쓰는 미묘한 관행은 피함
  • 임시 변수 하나를 서로 관련 없는 계산과 교환 작업에 함께 쓰면 두 사용이 관련된 것처럼 보임
  • 각 목적에는 그 목적을 드러내는 별도 변수를 사용함
  • 이름 없는 temp보다 discriminant, oldRoot처럼 의미를 드러내는 이름이 낫음

숨은 의미가 있는 변수 피하기

  • 특정 값이면 오류를 뜻하고, 다른 값이면 정상 값을 뜻하는 식의 이중 의미를 피함
  • 예를 들어 페이지 수가 -1이면 오류라는 식은 정수 변수가 불린 역할까지 겸하는 것임
  • 이런 남용은 hybrid coupling에 해당함
  • 두 종류의 정보를 담아야 한다면 두 변수를 사용함
  • 선언한 변수는 실제로 사용되는지 확인함
  • 미사용 변수는 결함률 상승과 관련이 있으므로 컴파일러나 도구 경고를 무시하지 않음

참고 자료

  • Cormen, Leiserson & Rivest, Introduction to Algorithms
  • Sedgewick, Algorithms in C++, Parts 1-4
  • Sedgewick, Algorithms in C++, Part 5

체크리스트: 데이터 사용 시 일반 고려 사항

변수 초기화

  • 각 루틴이 입력 매개변수의 유효성을 확인하는가?
  • 변수를 처음 사용하는 위치 가까이에 선언하는가?
  • 가능하다면 변수를 선언과 동시에 초기화하는가?
  • 선언과 동시에 초기화할 수 없다면 처음 사용하는 위치 가까이에 초기화하는가?
  • 카운터와 누산기가 올바르게 초기화되고, 필요하면 매번 재초기화되는가?
  • 반복 실행되는 코드에서 변수가 올바르게 재초기화되는가?
  • 컴파일러 경고를 모두 켠 상태에서 경고 없이 컴파일되는가?
  • 언어가 암시적 선언을 사용한다면 그로 인한 문제를 보완했는가?

그 밖의 데이터 사용 문제

  • 모든 변수가 가능한 가장 작은 범위를 갖는가?
  • 변수 참조가 다음 참조까지의 거리와 전체 생존 시간 양쪽에서 가능한 한 가까운가?
  • 제어 구조가 데이터형과 대응되는가?
  • 선언된 모든 변수가 사용되는가?
  • 모든 변수가 적절한 시점에 바인딩되는가? 늦은 바인딩의 유연성과 복잡성 사이에서 의식적으로 균형을 잡았는가?
  • 각 변수가 오직 한 가지 목적만 갖는가?
  • 각 변수의 의미가 명시적이며 숨은 의미가 없는가?

요점 정리

  • 데이터 초기화는 오류가 생기기 쉬우므로, 예상하지 못한 초기값 문제를 피하기 위한 초기화 기법을 사용해야 한다.
  • 각 변수의 범위를 최소화해라. 변수 참조를 가까이 두고, 루틴이나 클래스 안에 지역화하며, 전역 데이터는 피한다.
  • 같은 변수를 다루는 문장들은 가능한 한 가까이 둔다.
  • 이른 바인딩은 유연성을 제한하지만 복잡성을 줄인다. 늦은 바인딩은 유연성을 높이지만 복잡성을 증가시킨다.
  • 각 변수는 오직 한 가지 목적에만 사용한다.

results matching ""

    No results matching ""