[15/20] 구조체와 함수
글을 시작하기에 앞서 해당 시리즈는 Allen Downey, Ben Lauwens의 저서인 Think Julia: How to Think Like a Computer Scientist 를 바탕으로 작성된 글임을 알려드립니다.
이 포스트는 Structs and Functions를 한글로 요약 정리한 글입니다.
구조체와 함수 (Structs and Functions)
저번 장에서 새로운 복합 데이터 타입을 생성하는 방법에 대해서 알아보았다면, 지금부터는 개발자가 정의한 고유 객체들을 매개 변수로 사용하여 결과로 반환하는 함수를 작성해볼 것이다. 따라서 이번 장에서는 "함수적 프로그래밍 스타일(unctional programming style)"과 새로운 두 가지 프로그램 개발 계획을 볼 것이다.
시간 (Time)
복합 데이터 타입의 또 다른 에시로서, 시간을 기록하는 MyTime
을 struct
로 정의할 것이다. 코드는 아래와 같다.
1 |
|
Time
이라는 이름은 이미 줄리아에서 사용 중이기 때문에, 충돌을 방지하기 위해 MyTime
로 결정하였다. 이제 새로운 MyTime
객체를 생성할 수 있다.
1 |
|

MyTime
객체 다이어그램은 위와 같다.
순수 함수 (Pure Functions)
지금부터 시간 값을 추가하는 두 개의 함수를 작성할 것이다. 두 개의 함수 중 하나는 '순수 함수(pure functions)'이며 다른 하나는 '변경자(modifiers)'이다. 또한 이 함수들은 간단한 프로토타입으로 시작하여 복잡한 문제를 점진적으로 해결하는 방법인 '프로토타입 및 패치(prototype and patch)'를 호출하여 개발 계획도 시연할 것이다.
아래의 코드는 addtime
의 간단한 프로토타입이다.
1 |
|
이 함수는 MyTime
객체를 새로 생성한 후, 필드를 초기화하고 새로운 객체에 대한 참조를 반환한다. 이렇게 인수로 전달된 객체를 수정하지 않고 반환하는 함수를 '순수 함수'라고 한다. 즉, 인수로 전달된 객체들은 값을 제공하거나 보여지는 정도만 실행되고 어떤 영향도 받지 않는다.
위 함수를 테스트하기 위해서 두 개의 MyTime
객체를 만든다. start
에는 'Monty Python and the Holy Grail' 영화의 시작시간이 포함되고, duration
에는 영화의 상영 시간인 1시간 35분이 포함된다.
addtime
은 영화가 끝난 시간을 보여준다.
1 |
|
결과는 10:80:00
으로 우리가 원하던 시간은 아니다. 문제는 해당 함수가 분단위와 초단위가 60을 초과하는 경우를 처리하지 못한다는 것이다. 그렇게 되면 60분을 1시간으로, 60초를 1분으로 헤당 열에 반입해야 한다. 이를 해결한 코드는 아래와 같다.
1 |
|
위의 함수는 정확하게 작동하지만 코드가 복잡해졌다. 이후 더 짧은 대안들을 배울 것이다.
변경자 (Modifiers)
때로는 함수가 매개 변수로 얻는 객체를 수정하는 것이 더 유용하다. 이 경우에는 변경 사항이 호출자에게 보여진다. 이런 방식으로 작동하는 함수를 '변경자(Modifiers)'라고 한다.
인수인 'second'의 숫자를 MyTime
객체에 추가하는 increment!()
는 변경자를 사용하여 자연스럽게 작성할 수 있다. 초안은 아래와 같다.
1 |
|
첫 번째 줄은 기본적인 작동을 수행하며, 나머지 줄은 우리가 봤던 특별한 경우들을 처리한다.
이 함수는 정확한가? 만약 second
가 60보다 크면 어떻게 작동하는가?
함수가 제대로 작동하는지 확인하기 위해서는 time.second
가 60이 넘을 때까지 여러 번 실행해야 한다. 이를 위한 방법으로는 if
문을 while
문으로 바꾸는 것이다. 이것은 정확하게 작동하지만, 효과적이지는 않다.
프로토타이핑 vs. 계획 (Prototyping Versus Planning)
개발자가 시연하고 있는 개발 계획을 '프로토타입 및 패치(prototype and patch)'라고 한다. 각 함수에서 기본 계산을 실행하는 프로토타입을 만들어 테스트 한 후, 발생한 오류들을 패치한다.
이 방법은 문제가 무엇인지 깊게 이해하지 못한 경우에 특히 효과적이다. 그러나 점진적인 수정방법은 모든 오류를 한번에 볼 수 없기에, 가끔 불필요하게 복잡한 코드로 수정하도록 한다.
위 문제의 대안은 문제에 대한 높은 수준의 통찰력을 통해 프로그래밍이 훨씬 쉬워질 수 있도록 설계하는 개발방식이다. 이전 '초단위 문제'의 경우, 필요한 통찰력은 Time
객체가 육십진법의 3개의 숫자라는 것이다. 초단위 second
속성이 1열, 분단위 minute
속성이 60열, 시단위 hour
속성이 3600열이다.
addtime()
와 increment!()
를 작성할 때, 육십진법에서는 한 열에서 다음 열로 이동해야 하기 때문에 이를 효과적으로 추가하는 방식을 고려하고 싶다.
이런 생각은 전체적인 문제의 다른 접근법을 제안한다. MyTime
객체를 정수로 변환하고 컴퓨터의 계산을 활용하는 것이다.
아래의 함수는 MyTime
객체를 정수로 변환한다.
1 |
|
그리고 아래의 함수는 정수를 MyTime
으로 변환한다. (divrem
은 첫 번째 인수를 두 번째 인수로 나누고 몫과 나머지를 튜플로 반환한다)
1 |
|
위의 함수가 올바른지 확인하기 위해서는 약간의 생각과 테스트를 진행하면 된다. 테스트하는 방법은 timetoint(inttotime(x)) == x
가 많은 x
값에도 작동하는지 확인하는 것이다. 일관성 검사(consistency check)의 예이다.
위의 함수가 정확하다고 확신한다면, addtime
을 다시 작성하는데 사용할 수 있다.
1 |
|
해당 버전은 처음보다 훨씬 쉽고 명확하다.
어떤면에서 육십진법에서 십진법 또는 그 반대로 변환하는 것은 시간을 처리하는 것보다 어렵다. 기본 변환이 더 추상적이기 때문이다.
하지만 시간을 육십진법으로 취급하고 timetoint()
및 inttotime()
와 같은 변환 함수를 작성하는데 투자할 수 있는 통찰력만 있다면 더 짧고 읽기 쉬운 안정적인 프로그램을 만들 수 있다.
또한 나중에 함수를 추가하는 것이 더 쉽다. 예를 들어, 두 개의 MyTime
을 빼서 둘 사이의 지속 시간을 찾는다고 가정해보자. 가장 편한 방법은 두 개를 빼주는 것이다. 변환 함수를 사용하면 더 쉽고 정확하게 뺄 수 있다.
아이러니하게도, 때로는 문제를 더 일반화하여 만드는 것이 더 쉽다. 그 이유는 특별한 경우도 적고 오류 가능성도 적어지기 때문이다.
디버깅
MyTime
객체는 minute
과 second
의 값이 0부터 60사이(0은 포함, 60은 불포함)이고, hour
이 양수이면 올바르게 생성된다. minute
와 hour
는 값이 정수여야 하지만, second
은 소수도 가능하다.
이와 같은 요구사항들은 항상 참이여야 하기 때문에 고정 변수(invariants)라 불린다. 다른 방식으로 말하면, 만약 요구사항들이 참이 아니라면 무언가가 잘못되었다는 것이다.
1 |
|
고정 변수를 확인하는 코드를 작성하면 오류를 감지하고 원인을 찾을 수 있다. 예를 들어 isvalidtime()
가 MyTime
객체를 가져와서 고정 변수를 위반하면 false
를 반환한다.
각 함수의 시작 부분에 isvalidtime()
를 추가하여 인수가 유효한지 확인할 수 있다.
1 |
|
또는 주어진 고정 변수를 확인하고 실패하면 예외를 제공하는 @assert
매크로를 사용할 수 있다.
1 |
|
@assert
매크로는 정상적인 조건을 처리하는 코드와 오류를 확인하는 코드를 구분하기 때문에 유용하다.