프날 오토핫키 강좌  v2

⚠ 이 강좌는 오토핫키 v2를 다룹니다

지금 보시는 강좌는 과거 오랜 시간동안 알려진 오토핫키(v1.1)의 차세대 버전인 오토핫키 v2를 다루고 있습니다.
만약 구버전인 '오토핫키 v1.1'의 강좌를 찾으신다면 프날 오토핫키 강좌(https://pnal.kr)를 봐주시면 되지만, 새로 오토핫키를 배우신다면 v2 버전을 배우시는 것을 강력히 추천드립니다.

52. 나만의 함수 만들기


지금까지 강좌를 잘 오셨다면 간단한 자동화 프로그램 정도는 쉽게 만들 수 있을 것입니다. 그런데 프로그램이 조금만 길어져도 스크립트가 엄청나게 길어지고, 코드를 유지보수할 때 순차 진행되는 스크립트를 따라가려면 마우스를 계속 스크롤해야할 것입니다.

결국 스크립트를 구조화 할 필요가 있습니다. '조금씩 다르지만 같은 기능을 하는 부분'을 묶어서 작성해줄 수 있습니다. 그렇게 하면 눈에 잘 보이는 코드를 만들수 있습니다.

함수

함수는 하나의 기능을 하기 위해 여러 명령을 모아 작성해준 구조를 의미합니다. 지금까지 써준 내장 함수는 나름의 기능을 수행합니다. '알림 상자를 화면에 띄워준다', '컨트롤의 내용을 바꿔준다' 같은 기능이 내장 함수로 제공되죠.

기본적인 기능만 내장 함수로 정해져 있기에, 더욱 복잡한 기능을 위해선 함수를 만들어 줄 필요가 있습니다. 우리가 원하는 기능을 위해서 새 함수를 만들 수 있다는 뜻입니다. 예를 들어서, "이미지를 찾아 클릭한다"라는 함수를 만들어 줄 수 있습니다. 이렇게하면 ImageSearch + if + MouseClick을 매번 적지 않아도, 이것을 한번에 할 수 있는 함수를 호출하기만 하면 원하는 동작을 할 수 있죠.

오토핫키 함수의 정의

드디어 설명드리겠군요. 새 함수를 만드는 것을 '함수를 정의한다'라고 합니다. 함수는 함수명, 매개변수, 반환값으로 구성되어있습니다.

1FunctionName(param1, param2)
2{
3 [함수의 본문]
4 [함수의 본문]
5 ...
6 return returnValue
7}
함수의 정의 방법

위의 코드는 FunctionName 함수를 정의한 모습입니다. param1param2는 매개변수, returnValue는 반환값입니다. 매개변수는 여러 개가 올 수 있지만, 반환 값은 오직 한 개만 쓸 수 있습니다.

함수를 호출할 때 적어준 인수 값이 매개변수(parameter)로 전달됩니다. 함수 내는 또다른 '지역'입니다. 그렇기 때문에 바깥에서의 지역 변수를 그대로 쓸 수 없습니다. 그래서 인수를 전달해주어, 해당 인수의 값을 함수 내부에서 지역 변수로 사용할 수 있도록 하는 것입니다.

반환 값을 가지는 내장 함수를 이용해보았죠? 예를 들어서, ImageSearch는 화상을 찾으면 1, 그렇지 못하면 0을 반환했습니다. 이와 같은 반환값 또한 return 제어문을 통해 지정해줄 수 있습니다.

Tip: return 제어문

return 제어문은 함수의 값을 반환하는 것 뿐만 아니라, 코드 진행을 해당 지역에서부터 복귀하도록 지시하는 기능도 하고 있습니다. 예를 들어서, 함수나 핫키 지역 내에서 코드를 진행하다가 return을 만나면 그 밑의 부분은 실행하지 않습니다.

이와 관련된 내용은 추후 자세히 설명할 것이니, 지금은 우선 '함수에서 반환값을 지정하는 역할'이라고만 이해하시면 됩니다.

함수의 기본 동작

간단한 예제를 보며 함수의 동작을 이해해봅시다.

1MsgBox(Add(1, 2))
2
3Add(x, y)
4{
5 result := x + y
6 return result
7}
두 수를 더한 값을 반환해주는 Add 함수의 구현

위의 Add 함수는 두 값을 인수로 받아서, 더한 값을 반환해주는 함수입니다. 1번 줄에서 Add(1, 2)로 함수를 호출하면, Add(x, y) 함수가 실행되면서 x, y 변수에 각각 인수인 1, 2를 담습니다. 인수가 전달되는 변수 x, y매개변수라고 하며, 함수 내에선 매개변수를 이용하여 이것저것 작업을 한 다음 반환값을 반환하죠. 이를 그림으로 표현하면 아래와 같습니다.

사진 1. Add 함수의 동작

물론, 위의 스크립트는 함수의 각 부분을 구별하기 쉽게 하기 위해 함수의 내용을 늘려보았지만, 쓸데없는 지역 변수를 사용하지 않는 편이 더욱 좋기 때문에 아래처럼 쓰는 것이 더욱 좋습니다.

1MsgBox(Add(1, 2))
2
3Add(x, y)
4{
5 return x + y
6}
return 옆에 식을 적어주어 보기좋게 수정한 모습

매개변수와 반환값의 생략

매개변수나 반환값이 필요 없으면 생략할 수 있습니다. 예를 들어서, 아래와 같은 코드 조각들은 각각 매개변수가 없는 함수, 반환값이 없는 함수, 둘 모두 없는 함수의 모습입니다.

1FunctionName()
2{
3 MsgBox("함수가 호출되었습니다.")
4 return 1
5}
매개변수가 없는 함수
1FunctionName(param)
2{
3 MsgBox(param "이 전달되었습니다.")
4}
반환값이 없는 함수
1FunctionName()
2{
3 MsgBox("함수가 호출되었습니다.")
4}
매개변수와 반환값이 없는 함수

필요에 따라 매개변수와 반환값을 적절히 사용해주시면 되겠습니다. 바깥의 값을 함수에서 이용하려면 매개변수를 이용하여 값을 전달받아야 하며, 함수에서 바깥으로 값을 내보내려면 반환값을 통해서 전달해야 하는 것을 원칙으로 합니다.

Tip: 인수와 매개변수의 차이

아주 초반에서 '매개변수'란 어떤 인수를 넣어야 하는지 표시해주는 이름이라고 언급한 적 있습니다. 그때는 함수가 어떻게 동작하는지 알지 못했으므로(심지어는 변수가 무엇인지도 배우지 않았으므로!), 그것이 최선의 설명이었음을 이해해주시길 바랍니다.

이제 제대로 설명할 수 있겠군요. 인수(Argument)는 함수를 호출할 때 적어주는 실질적인 값을 의미하며, 매개변수(Paremeter)는 함수 내에서 사용되는, 인수가 전달된 지역 변수를 의미합니다.

따라서 위의 Add 함수에서 x, y는 매개변수이며 호출 시 작성한 1과 2는 인수입니다.

실습

1. 주어진 값에 따라 반복 횟수가 달라지는 반복문

주어진 값에 따라 반복문의 반복 횟수를 다르게 하는 함수를 구성하여 호출해봅시다.

1LoopMsg(3)
2LoopMsg(5)
3LoopMsg(2)
4
5LoopMsg(count)
6{
7 Loop count
8 MsgBox(A_Index "회 반복 중")
9}
예제 1. 주어진 값으로 반복 횟수를 결정

5번 줄부터 함수가 정의되어있습니다. 매개변수로 전달받은 값을 반복문의 반복 횟수로 지정해주고 있습니다. 호출 시에 인수를 전달하면 그 인수만큼 반복하겠죠. 따라서, 위의 예제는 반복문을 세 번, 다섯 번, 두 번 반복할 것입니다. 1, 2, 3번줄에서 3, 5, 2를 인수로 함수를 호출하기 때문이죠.

참고로, 함수를 이용해도 당연히 순차 진행한다는 원칙을 지킵니다. 단지 함수 호출을 하면 함수를 잠시 들렀다가 온다고 여기시면 됩니다. 위의 예제는 아래와 같은 줄 순서로 수행되겠죠.

1 → 7 → 8 → 8 → 8 → 2 → 7 → 8 → 8 → 8 → 8 → 8→ 3 → 7 → 8 → 8 → 4(=종료)

밑줄 친 줄 번호는 함수를 호출한 줄이며, 나머지는 함수 내부에서의 줄 진행임

반환 값이 필요하지 않으므로 return 구문은 생략했다는 점도 눈여겨볼만 합니다.

2. 절댓값을 만들어주는 함수

어떤 수가 전달되든 절댓값을 만들어주는 함수를 구현해보겠습니다. 학창시절에 배운 '절댓값', 기억하시는 분 계신가요? 절댓값은 수직선 위에서 해당 수가 얼마나 떨어져있는지를 나타낸 수인데, 양수는 그 자체가 절댓값이고 음수는 양수로 바꾸면 절댓값이 됩니다. 예를 들어서 -3의 절댓값은 3이죠.

아무튼, 오토핫키에선 Abs()라는 내장 함수를 제공하여 절댓값 변환을 쉽게 할 수 있습니다. 그렇지만 한번 직접 구현해보겠습니다.

1MsgBox(GetAbs(-2))
2MsgBox(GetAbs(2))
3
4GetAbs(originalNumber)
5{
6 if (originalNumber < 0)
7 originalNumber := -originalNumber
8 return originalNumber
9}
예제 2. 인수 값이 음수면 양수로 바꿔주고, 양수면 그대로를 반환하는 예제

함수를 제대로 이해했다면 그리 어렵지 않을 것입니다. 매개변수의 값을 비교해서, 0보다 작다면 앞에 마이너스 기호를 붙여주어 양수로 만들어줍니다. 이 예제의 결과는 2, 2가 되겠네요.

아래와 같이, return 구문을 두 군데 써주는 방법도 있습니다. 함수의 경우 return을 만나면 즉시 동작을 멈추고 호출부로 돌아가기 때문에(추후 설명), 아래와 같이 작성해도 문제가 없습니다.

1MsgBox(GetAbs(-2))
2MsgBox(GetAbs(2))
3
4GetAbs(originalNumber)
5{
6 if (originalNumber < 0)
7 return -originalNumber
8 return originalNumber
9}

저는 반환을 한 군데에서 하는 것을 좋아하기 때문에, 코드가 크게 더러워지는 경우가 아니라면 이 방법은 선호하지 않습니다. 물론 반환 지점을 여러 군데로 나누어 작성해야 더욱 깔끔한 경우도 있습니다.

3. 찾은 화상을 클릭하는 함수

ImageSearch 함수를 이용하여 화상을 찾을 수 있었습니다. 찾은 이미지를 클릭하는 작업을 할 때마다 ifMouseClick으로 일일이 적어주기엔 너무 코드가 복잡해집니다. 찾은 화상을 클릭하는 함수를 만들어봅시다.

1ClickImage(100, 100, 400, 400, "Image.png")
2ClickImage(600, 200, 900, 500, "Image.png")
3ClickImage(500, 500, 800, 800, "ImageDiff.png")
4
5ClickImage(startX, startY, endX, endY, imageFile)
6{
7 if (ImageSearch(&vx, &vy, startX, startY, endX, endY, imageFile))
8 MouseClick("L", vx, vy)
9}
예제 3. 화상을 클릭해주는 함수

이해는 어렵지 않으실겁니다. 찾을 범위와 이미지 파일의 이름을 인수로 전달해주면, 이를 이용하여 ImageSearch를 수행하여 찾은 부분을 클릭하여줍니다.

이 예제는 함수가 필요한 이유를 잘 보여줍니다. 만약 함수를 쓰지 않으면, 아래와 같이 쓸 수 밖에 없습니다.

1if (ImageSearch(&vx, &vy, 100, 100, 400, 400, "Image.png"))
2 MouseClick("L", vx, vy)
3if (ImageSearch(&vx, &vy, 600, 200, 900, 500, "Image.png"))
4 MouseClick("L", vx, vy)
5if (ImageSearch(&vx, &vy, 500, 500, 800, 800, "ImageDiff.png"))
6 MouseClick("L", vx, vy)

같은 부분이 숫자만 바뀌며 반복되는데, 완전히 같은 구문이 아니기에 반복문을 쓸 수는 없습니다. '화상을 클릭한다'라는 동작을 수정할 경우, 세 군데 모두를 수정해주어야합니다.

만약 이런 동작이 10개가 넘어가면 어떨까요? 한 동작에 두 줄씩 작성하므로, 10개면 총 20줄일 것입니다. 동작이 길어질수록 코드를 유지보수하느라 엄청난 노동력이 소모될 것입니다. 그것도 코드에 변경점이 있을 때마다 그런 고생을 해야합니다.

하지만 함수를 사용하여 '화상을 클릭한다'라는 기능을 묶어놓으면, 보기에도 훨씬 깔끔한 것은 물론이거니와 함수의 정의만 수정하면 모든 호출시 동작이 바뀌기 때문에 유지보수에도 용이합니다.

무엇보다, 함수는 다시 재사용할 수 있죠. 만약 "(100, 200) 부터 (300, 400)까지에서 B.png를 찾아 클릭해" 라는 동작을 추가할 때를 살펴보면, 함수를 사용하면 아래와 같이 한 줄만 추가하면 됩니다.

1ClickImage(100, 200, 300, 400, "B.png")
함수가 정의되어 있을 경우

그렇지 않다면, 아래와 같이 그 동작을 다시 반복해서 적어주어야 하죠.

1if (ImageSearch(&vx, &vy, 100, 200, 300, 400, "B.png"))
2 MouseClick("L", vx, vy)

동작이 길어지면 길어질수록 더욱 비효율적인 일이 될 것입니다. 이렇듯 가능한 한 함수로 동작을 묶어주는 것이 중요합니다. 결국 죽 늘여서 코드를 작성하는 것이 아닌, 함수를 작성하여 그 함수를 호출하는 식으로 프로그램을 작성하는 것이 좋습니다.

Tip: 함수로 묶는 동작의 크기

함수는 한 가지만 해야한다는 원칙이 있습니다. 함수가 그보다 커지면 안됩니다. 예를 들어 "화상이 있을 때 클릭한 후 로그 파일을 생성한다"를 하나의 함수로 만들면 안됩니다. 로그 파일을 만드는 부분은 별도의 함수로 만들어서, 그 함수를 호출해야합니다.

함수 안의 동작이 커진다면 함수를 쪼개는 것이 기본이며, 더욱 아름다운 코드를 위해선 객체와 클래스에 관한 깊은 이해가 필요합니다. 기초 강좌인 프날 오토핫키에서는 이에 관해 Part 3에서 아주 약간만 강좌합니다.

가능한 한 모두 함수로 만들어서, 작은 크기의 함수를 호출해가는 것이 프로그래밍을 처음 접한 여러분이 현재 할 수 있는 가장 좋은 코드일 것입니다. 이렇게 프로그램을 작게 쪼개는 것을 '모듈화'라고 하는데, 프로그래밍에서 중요하게 여겨지는 개발 원칙 중 하나입니다.

명심하세요. 함수는 한 가지만 해야 합니다.

질문하러 가기