Computer Science/Algorithm

[Algorithm] 코딩과 디버깅에 관하여

ooeunz 2019. 10. 21. 18:50
반응형

Programmers

 읽기 쉬운 코드

코딩 테스트에서 좋은 성적을 내기 위한 비결을 당장 빨리 코드를 작성하는 것이 아니라 읽기 쉬운 코드를 작성하는 것이다. 복잡하고 읽기 어려운 코드는 디버깅에 있어서 어려움이 있고, 한 번에 정확하게 작성하기도 어렵기 때문이다. 아래는 특별히 중요한 몇 가지 원칙들에 관하여 설명한다.

 

전역 변수의 광범위한 사용

전역 변수를 많이 사용하면 프로그램의 흐름을 파악하기 어려워지고 때문에 대개 사용을 지양한다. 하지만 짧은 시간 안에 알고리즘을 평가하는 코딩 테스트의 경우에는 작성하는 코드의 구조가 매우 단순하고 각 변수를 읽고 쓰는 부분이 어디인지 비교적 명확하기 때문에 전역 변수를 사용하더라도 잃는 것이 많지 않다.

 

적극적인 코드 재사용

간결한 코드를 작성하기 위한 가장 직접적인 방법은 코드를 모듈화 하는 것이다. 같은 코드가 세 번 이상 등장한다면 상상 해당 코드를 함수나 클래스로 분리해 사용한다는 원칙을 만들면 좋다. 이상적인 세계에서는 한 함수가 두 가지 이상의 일을 해서는 안 된다고 한다. 하지만 코딩 테스트 환경에서는 이 정도로 엄격하게 원칙을 따르지는 못한다.

 

표준 라이브러리 사용

학교 자료 구조 수업을 기준으로 공부하던 많은 학생들이 가장 많이 하는 실수가 실제 시험장에서 자료 구조, 혹은 정렬 등의 기초적 알고리즘을 직접 작성하는 것이다. 대부분의 언어는 이미 표준 라이브러리들이 검증되어 있고, 메모리 관리나 정당성 증명에 신경 쓸 필요 없이 편하게 사용할 수 있다.

 

항상 같은 형태로 프로그램 작성

알고리즘 문제를 풀다보면 비슷한 기능을 하는 코드를 다양한 형태의 코드로 짜는 경우가 있다. 이러한 코드는 실수의 원이 되기도 한다. 때문에 자주 작성하는 알고리즘이나 코드 등에 대해서는 한 번 검증된 코드를 작성하고, 꾸준히 사용할 필요가 있다.

 

함수 또는 변수의 네이밍을 명료하게 할 것

함수와 변수의 이름을 어떠한 기능을 하는지, 어떤 용도인지 이름만으로 단번에 구분할 수 있도록 명료하게 짓도록 한다. 특히 파이썬으로 코드를 작성할 경우 마치 영어 문장을 읽는 듯하게 코드를 짤 수 있는데, 이럴 경우 명료하지 않은 함수명 등은 실수의 원인이 되기도 한다.

 

모든 자료를 정규화해서 저장할 것

같은 자료를 두 가지 형태로 저장하지 않는다. 예를 들어 유리수는 항상 약분해 기약 분수로 표현하는 것이 좋다. 또는 -30도를 표현하는 방법에는 -30도, 330도, 690도 등이 있다. 또는 시간을 표현할 때 어떤 시간대 기준을 하느냐의 차이도 있다. 때문에 이러한 자료를 정규화해서 저장할 것을 권고한다. 또 한 가지의 팁으로 외부에서 문자열을 읽어드리게 되면 먼저 UTF-16이나 UTF-8 인코딩으로 변환해서 사용한다.

 

코드와 데이터 분리할 것

날짜를 다루는 프로그램을 작성할 때 날짜를 출력할 때 보통 프로그래밍에 익숙하지 않은 사람들은 열두 줄 짜리 if문으로 이루어진 코드를 짠다.

def get_month_name(month):
	if month == 1:
    	return "January"
    elif month == 2:
    	return "February"
    elif month == 3:
    	return "March"
...

하지만 위와 같은 코드를 아래와 같은 방법으로 코드의 양을 줄일 수 있다.

const month_name = ["January", "February", "March"...]

대표적인 또 다른 예가 체스같은 보드 게임이 있다. 체스판 상에서 말들의 움직임을 다루는 문제를 풀 경우 각 말이 움직일 수 있는 위치를 프르그램으로 작성하는 대신 상대 좌표를 배열에 저장해두면 좋다.

const knightDx = [2, 2, -, 2, 1, 1, -1, -1]
const knightDy = [1, -1, 1, -1, 2, 2, 2, -2]

 

범위 표현 방식

대부분의 프로그래밍 언어는 반 열린 구간(half-open interval)을 사용한다. 반 열린 구간이란 첫 번째 값은 집합 안에 포함하고, 다른 하나는 집합 안에 포함되지 않는다. 이러한 범위 표현 방식은 세 가지 장점을 가지고 있다.

  1. 첫 번째 값과 마지막 값이 같은 구간을 이용하면 텅 빈 구간을 쉽게 표현할 수 있다.
  2. 두 구간이 연속해 있는지를 쉽게 알 수 있다. 예를 들어 (a, b)와 (c, d)가 연속되어 있는지 보려면 b=c혹 혹은 a=b인지 확인하면 된다.
  3. 구간의 크기를 쉽게 알 수 있다. 예를 들어 (a, b)로 표현된 구간이 있으면 구간에 포함된 자연수의 수는 b-a가 된다.

Off-by-one

Off-by one 오류는 계싼의 큰 줄기는 맞지만 하나가 모자라거나 한가 많아서 클리는 코드들의 오류들을 모두 가리킨다. 이러한 실수를 방지하는 방법은 최소 입력이 주어졌을 때 이 코드가 어떻게 동작할지를 되새겨 보며 프로그램을 짜는 것이다.

 

스택 오버플로

스택 오버플로는 대개 재귀 호출의 깊이가 허용 가능한 스택의 량을 초과할 때 발생한다. 스택 최대 크기는 컴파일이나 실행 시에 설정할 수 있고 기본 값이 언어나 아키텍처 등에 따라 매우 다르기 때문에 사용하는 환경의 스택 허용량에 대해 알아둘 필요가 있다.

 

최소, 최대 예외 잘못 다루기

쉽게 말해 '예외를 제대로 처리하지 못한 경우'이다. 이러한 경우 가능한 입력 중 최소 값과 최대 값을 입력해서 제대로 동작할지 생각해 보면 오류를 잡을 수 있는 경우가 꽤 있다.

 

변수 초기화 문제

이전 입력에서 사용한 전역 변수 값을 초기화하지 않고 그대로 사용하는 것이 해당된다. 이러한 경우 예제 입력 파일을 두 번 반복해 쓰는 것으로 점검해 볼 수 있다.

 

 


 

디버깅

대부분의 사람들은 디버깅이라 한다면 에디터에서 디버거를 키고 한 줄씩 실행하는 것을 떠올린다. 하지만 눈으로 디버깅을 하는 쪽이 훨씬 빠른 경우가 적지 않고, 재귀 호출과 같은 경우는 디버거로 디버깅을 하는 것이 쉽지 않다. 이러한 경우 디버거를 사용하는 대신 다음과 같은 단계를 밟는 것이 좋다.

  1. 작은 입력에 대해 제대로 실행되나 확인
  2. 단정문(assertion)을 쓴다. 단정문이란 주어진 조건이 거짓일 때 오류를 태고 프로그램을 강제 종료시키는 구문이나 함수를 의미한다.
  3. 프로그램의 계산 중간 결과를 출력한다.

만약 이런 과정을 거쳤음에도 문제가 어디서 일어났는지 알 수 없거나, 런타임 에러 나고 종료가 되는 경우는 디버거를 사용해도 좋다.

 

테스트

당연한 말이지만 답안을 작성한 이후 제출 전에 가능한 많은 예제로 프로그램을 테스트 하는 것이 좋다. 주어진 예제 외에도, 주어진 예제 입력을 약간 바꿔서 넣어 보거나, 있을 수 있는 가장 작은 입력과 가장 큰 입력을 만들어서 넣어보고 확인하는 것이 좋다.

스캐폴딩(scaffolding)이라는 말이 있다. 건축쪽의 언어지만 프로그래밍에서는 개발할 때 뼈대를 잡기 위해 임시로 사용하는 코드라는 뜻이다. 예를 들어 직접 작성한 정렬 함수를 테스트하려고 할 때, 하나하나 입력을 손으로 넣는 방법도 있지만, 임의의 입력을 주어서 프로그래밍적으로 입력하여 프로그램을 테스트해볼 수 있다.

 

너무 큰 '무한대' 값

프로그램을 짜다보면 무한대에 해당하는 큰 값을 이용하는 것이 편리할 때가 있다. 예를 들어 어느 곳에 가는 가장 짧은 길(min)을 찾고 있는데, 길이 없는 경우 실제로 나타날 리 없는 매우 큰 값을 반환을 통해 min()에서 걸러지게 하여 예외 처리 없이 간편하게 코드를 짤 수 있다.

반응형