본문 바로가기
Golang/Tucker

[묘공단] 2주차 (2)

by 윤원용 2023. 10. 9.

이 글은 골든래빗 《Tucker의 Go 언어 프로그래밍》의 09~11장 써머리입니다.

 

이제부터 프로그래밍의 꿀잼이 시작된다. 그러므로 책에 없는 예제 위주로 리뷰할고 밑에 더 보기를 클릭하여 책에서는 어떤 목차들로 구성됐는지 확인해 보면 좋을 것 같다.   

 

예제에 대해 간단하게 설명하면 if문, switch문, for문을 사용하여 fmt 패키지의 Scanf를 사용하여 입력된 값을 간단한 계산하는 계산기를 코딩하여 if문으로 만든 것을 switch문으로 변경해 보고 for문을 사용하여 기능 추가하는 예제이다.

 

더보기
  • 09 if문
    • 9.1 if문 기본 사용법
    • 9.2 그리고 &&, 또는 ||
    • 9.3 중첩 if
    • 9.4 if 초기문; 조건문
  • 10 switch문
    • 10.1 switch문 동작 원리
    • 10.2 switch문을 언제 쓰는가?
    • 10.3 다양한 switch문 형태
    • 10.4 const 열거값과 switch
    • 10.5 break와 fallthrough 키워드 (pass)
  • 11 for문
    • 11.1 for문 동작 원리
    • 11.2 continue와 break
    • 11.3 중첩 for문
    • 11.4 중첩 for문과 break, 레이블 (pass)

9. if문

if문은 ==, !=, <=, >=, &&, ||, ! 과 같은 논리연산자들을 이용해 bool 값을 통해 분기처리하기 위해 사용된다. 다른 언어에선 if (조건식) 이런 식으로 소괄호에 조건식을 넣는데 Golang은 소괄호 없이 if 조건식으로 사용되는 점과 다른 언어의 while문 처럼 변수 선언과 조건문을 한 번에 할 수도 있다. 이론은 여기까지고 예제 코드를 보면 바로 알 수 있기 때문에 예제코드로 바로 넘어가겠다.

package main

import "fmt"

func main() {
        var (
                leftOperand int64
                rightOperand int64
                operation int
        )

        fmt.Println("피연산자 두 개를 입력해 주세요.")
        fmt.Printf("왼쪽 피연산자: ")
        fmt.Scanf("%d", &leftOperand)
        fmt.Printf("오른쪽 피연산자: ")
        fmt.Scanf("%d", &rightOperand)

        fmt.Println("연산자를 선택해주세요.")
        fmt.Println("1. +, 2. -, 3. *, 4. /, 5. %, 6. **")
        fmt.Scanf("%d", &operation)

        fmt.Println("결과: ", calc(leftOperand, rightOperand, operation))
}

func calc(left, right int64, operation int) string {
        var result int64
        if operation == 1 {
                result = left + right
        } else if operation == 2 {
                result = left - right
        } else if operation == 3 {
                result = left * right
        } else if operation == 4 {
                result = left / right
        } else if operation == 5 {
                result = left % right
        }

        operationStr := getOperationStr(operation)
 
        return fmt.Sprintf("%d %s %d = %d", left, operationStr, right, result)
}

func getOperationStr(operation int) (op string) {
        if operation == 1 {
                op = "+"
        } else if operation == 2 {
                op = "-"
        } else if operation == 3 {
                op = "*"
        } else if operation == 4 {
                op = "/"
        } else if operation == 5 {
                op = "%"
        }

        return
}

위 코드를 보면 if문과 else if 문으로 operation의 값에 따라 산술연산을 하거나 문자로 변경하고 있다. 예제를 실행해 보면 

위와 같은 결과가 나오게 된다. 하지만 코딩은 언제나 예외가 발생하는데 만약 왼쪽 피연산자를 10, 오른쪽 피연산자를 0으로 넣고 연산자를 4, 5번을 입력 시 예외가 발생하기 때문에 어떤 값을 입력받는 로직이라면 항상 이러한 예외를 대비해야 한다.

 

이럴 때 사용되는 게 if문이다. switch도 할 수 있지만 if문이 적합하기 때문에 실무에서 if문으로만 사용한다 생각해도 괜찮을 것이다. 그럼 어떤 예외가 발생하는지 확인해 보고 예외가 발생하지 않도록 수정해 보자.

이렇게 panic이라는 것이 발생하는데 정수를 나눌 때 0으로 나눌 수 없다.라고 예외가 발생하고 있고 이것을 해결하도록 코드를 수정해 보겠다. panic은 후반부에 나오니 그때 리뷰하기 위해 지금은 생략하겠다.

func main() {
        var (
                leftOperand int64
                rightOperand int64
                operation int
        )

        fmt.Println("피연산자 두 개를 입력해 주세요.")
        fmt.Printf("왼쪽 피연산자: ")
        fmt.Scanf("%d", &leftOperand)
        fmt.Printf("오른쪽 피연산자: ")
        fmt.Scanf("%d", &rightOperand)

        fmt.Println("연산자를 선택해주세요.")
        fmt.Println("1. +, 2. -, 3. *, 4. /, 5. %, 6. **")
        fmt.Scanf("%d", &operation)

        result := ""
        if (operation == 4 || operation == 5) && rightOperand == 0 {
                result = fmt.Sprintf("%d %s %d는 연산할 수 없습니다.", leftOperand, getOperationStr(operation), rightOperand)
        } else {
                result = calc(leftOperand, rightOperand, operation)
        }

        fmt.Println("결과: ", result)
}

간단하게 main함수에서 계산하기 전 입력된 operation이  4번이거나 5번인 경우 rightOperand가 0이면 계산을 하지 않고 연산할 수 없다는 메시지를 result에 대입하였고 조건이 거짓이면 계산할 수 있도록 else를 사용했다. else는 if문과 else if문의 조건식들에 부합하지 않은 경우 무조건 실행되는 영역이다.
이렇게 수정하고 실행해 보면

위 이미지처럼 예외(panic)가 발생하지 않고 간단하게 예외처리를 할 수 있게 됐다. 하지만 이렇게 수정하더라도 연산자를 6으로 입력한 경우에는 원치 않은 동작이 작동하게 되는데

위 이미지처럼 출력되니 이 부분도 고쳐보도록 하자.

func main() {
        var (
                leftOperand int64
                rightOperand int64
                operation int
        )

        fmt.Println("피연산자 두 개를 입력해 주세요.")
        fmt.Printf("왼쪽 피연산자: ")
        fmt.Scanf("%d", &leftOperand)
        fmt.Printf("오른쪽 피연산자: ")
        fmt.Scanf("%d", &rightOperand)

        fmt.Println("연산자를 선택해주세요.")
        fmt.Println("1. +, 2. -, 3. *, 4. /, 5. %, 6. **")
        fmt.Scanf("%d", &operation)

        operationStr := getOperationStr(operation)

        result := fmt.Sprintf("%d번은 잘 못된 입력입니다.", operation)

        if operation == 6 {
                result = fmt.Sprintf("%d(%s)번 연산자는 지원하고 있지 않습니다.", operation, operationStr)
        } else if (operation == 4 || operation == 5) && rightOperand == 0 {
                result = fmt.Sprintf("%d %s %d는 연산할 수 없습니다.", leftOperand, operationStr, rightOperand)
        } else if operation > 0 && operation < 7 {
                result = calc(leftOperand, rightOperand, operation)
        }

        fmt.Println("결과: ", result)
}

이렇게 수정하면 연산자(operation)의 입력 값이 1보다 작고 6보다 큰 수인 경우를 발생하는 예외를 기본으로 설정하여 if문을 줄이고 여러 예외 상황을 if문을 사용하여 예외처리를 할 수 있다.

 

10. switch문
본인이 생각했을 때 switch문은 공통적이 주제(로직 또는 상태)를 다루는 데이터들을 변환해 주거나 분기처리할 때 사용하면 좋다 생각한다. 기존 예제 코드에서 그런 부분들을 수정해 보도록 하겠다.

package main

import "fmt"

func main() {
        var (
                leftOperand int64
                rightOperand int64
                operation int
        )

        fmt.Println("피연산자 두 개를 입력해 주세요.")
        fmt.Printf("왼쪽 피연산자: ")
        fmt.Scanf("%d", &leftOperand)
        fmt.Printf("오른쪽 피연산자: ")
        fmt.Scanf("%d", &rightOperand)

        fmt.Println("연산자를 선택해주세요.")
        fmt.Println("1. +, 2. -, 3. *, 4. /, 5. %, 6. **")
        fmt.Scanf("%d", &operation)

        opcode := getOpcode(operation)

        result := fmt.Sprintf("%d번은 잘 못된 입력입니다.", operation)

        if operation == 6 {
                result = fmt.Sprintf("%d(%s)번 연산자는 지원하고 있지 않습니다.", operation, opcode)
        } else if (operation == 4 || operation == 5) && rightOperand == 0 {
                result = fmt.Sprintf("%d %s %d는 연산할 수 없습니다.", leftOperand, opcode, rightOperand)
        } else if operation > 0 && operation < 7 {
                result = calc(leftOperand, rightOperand, opcode)
        }

        fmt.Println("결과: ", result)
}

func calc(left, right int64, opcode string) string {
        var result int64
        switch opcode {
        case "+":
                result = left + right
        case "-":
                result = left - right
        case "*":
                result = left * right
        case "/":
                result = left / right
        case "%":
                result = left % right
        case "**":

        }
        return fmt.Sprintf("%d %s %d = %d", left, opcode, right, result)
}

func getOpcode(operation int) (opcode string) {
        switch operation {
        case 1:
                opcode = "+"
        case 2:
                opcode = "-"
        case 3:
                opcode = "*"
        case 4:
                opcode = "/"
        case 5:
                opcode = "%"
        case 6:
                opcode = "**"
        }
        return
}

로직은 변하지 않았지만 getOperationStr 함수를 getOpcode로 변경했고 calc 함수와 같이 if문을 switch문으로 변경했다. switch문으로 변경한 코드와 기존의 if문 코드를 비교해 보면 switch문이 코드의 양도 적고 가독성이 if문 보다 더 좋다는 것을 알 수 있다.

 

참고로 본인은 switch문을 사용할 땐 default 문을 잘 안 쓰는 편인데 이건 생략하고 switch문을 설명하면 switch 기준값 방식과 switch 변수 선언 및 초기화 방식이 있다. 개인적으로 이렇게 1, 2, 3, 4, ... 이런 식으로 순차적으로 로직을 분리할 땐 array를 쓰는 경우가 많기 때문에 switch 예제엔 안 맞을 수도 있다. 

 

본론으로 돌아와 예제코드에서 열거형(상수)을 사용하여 코드를 수정해 보겠다.

package main

import "fmt"

const (
        PLUS string = "+"
        MINUS string = "-"
        MULTI  string = "*"
        DIV string = "/"
        REMAINDER string = "%"
        SQUARE string = "**"
)

const (
        PLUS_NO int = 1
        MINUS_NO int = 2
        MULTI_NO int = 3
        DIV_NO int = 4
        REMAINDER_NO int = 5
        SQUARE_NO int = 6
)

func main() {
        var (
                leftOperand int64
                rightOperand int64
                operation int
        )

        fmt.Println("피연산자 두 개를 입력해 주세요.")
        fmt.Printf("왼쪽 피연산자: ")
        fmt.Scanf("%d", &leftOperand)
        fmt.Printf("오른쪽 피연산자: ")
        fmt.Scanf("%d", &rightOperand)

        fmt.Println("연산자를 선택해주세요.")
        fmt.Println("1. +, 2. -, 3. *, 4. /, 5. %, 6. **")
        fmt.Scanf("%d", &operation)

        opcode := getOpcode(operation)

        result := fmt.Sprintf("%d번은 잘 못된 입력입니다.", operation)

        if operation == SQUARE_NO {
                result = fmt.Sprintf("%d(%s)번 연산자는 지원하고 있지 않습니다.", operation, opcode)
        } else if (operation == DIV_NO || operation == REMAINDER_NO) && rightOperand == 0 {
                result = fmt.Sprintf("%d %s %d는 연산할 수 없습니다.", leftOperand, opcode, rightOperand)
        } else if operation > 0 && operation < 7 {
                result = calc(leftOperand, rightOperand, opcode)
        }

        fmt.Println("결과: ", result)
}

func calc(left, right int64, opcode string) string {
        var result int64
        switch opcode {
        case PLUS:
                result = left + right
        case MINUS:
                result = left - right
        case MULTI:
                result = left * right
        case DIV:
                result = left / right
        case REMAINDER:
                result = left % right
        case SQUARE:

        }
        return fmt.Sprintf("%d %s %d = %d", left, opcode, right, result)
}

func getOpcode(operation int) (opcode string) {
        switch operation {
        case PLUS_NO:
                opcode = PLUS
        case MINUS_NO:
                opcode = MINUS
        case MULTI_NO:
                opcode = MULTI
        case DIV_NO:
                opcode = DIV
        case REMAINDER_NO:
                opcode = REMAINDER
        case SQUARE_NO:
                opcode = SQUARE
        }
        return
}

예제 자체가 간단한 로직이기 때문에 큰 차이를 못 느낄 수 있지만 1, 2, 3이나 "+", "-"과 같은 리터럴 데이터보단 PLUS_NO, MINUS_NO와 같은 텍스트가 사람이 이해할 때 더 편하기 때문에 실무에서는 열거형을 꽤 많이 사용하는 편이다. 다른 예를 들면 "XXX", "XX", "XXXXX" 같은 문자열이 있을 때 X3, X2, X5가 이해가 빠르듯 비슷한 개념이라 생각하면 되고 이러한 코드들이 여러 곳에 분포돼 있거나 다른 곳에서 코딩하다 "XxX" 이런 식으로 오타 발생을 예방하기도 매우 좋기 때문에 열거형(상수)을 많이 사용하는데 이때 같이 사용하는 게 switch다. main함수에서 입력값을 확인하는 if문에도 열거형을 사용하고 있지만 딱히 좋아 보이지 않다고 느낄 수 있다. 그래도 개발 규모가 커지면 커질수록 1, 2, 3 보단 좋을 것이다. 

 

11. for문

for문은 프로그래밍 시 없으면 안 되는 경우가 훨씬 많기 때문에 앞으로 많이 이야기할 테니 지금은 설명을 많이 하는 것보다 기존 계산기 예제, 간단한 예제 이렇게 두 가지로 마무리하겠다.

 

기존 계산기 예제에서 제곱 기능을 추가해 보겠다.

func pow(base, cnt int64) int64 {
        var result int64 = 1
        for {
                result *= base
                cnt--
                if cnt == 0 {
                        return result
                }
        }
}

무한 루프를 사용했고 base는 rightOperand, cnt는 leftOperand이다. cnt가 0이면 지수만큼 계산이 끝났기 때문에 무한 루프를 종료시키기 위해 바로 result변수를 반환하도록 코딩했다. (짧고 좋다 ㅋ) 

전체 코드는

package main

import "fmt"

const (
        PLUS string = "+"
        MINUS string = "-"
        MULTI  string = "*"
        DIV string = "/"
        REMAINDER string = "%"
        SQUARE string = "**"
)

const (
        PLUS_NO int = 1
        MINUS_NO int = 2
        MULTI_NO int = 3
        DIV_NO int = 4
        REMAINDER_NO int = 5
        SQUARE_NO int = 6
)

func main() {
        var (
                leftOperand int64
                rightOperand int64
                operation int
        )

        fmt.Println("피연산자 두 개를 입력해 주세요.")
        fmt.Printf("왼쪽 피연산자: ")
        fmt.Scanf("%d", &leftOperand)
        fmt.Printf("오른쪽 피연산자: ")
        fmt.Scanf("%d", &rightOperand)

        fmt.Println("연산자를 선택해주세요.")
        fmt.Println("1. +, 2. -, 3. *, 4. /, 5. %, 6. **")
        fmt.Scanf("%d", &operation)

        opcode := getOpcode(operation)

        result := fmt.Sprintf("%d번은 잘 못된 입력입니다.", operation)

        if (operation == DIV_NO || operation == REMAINDER_NO) && rightOperand == 0 {
                result = fmt.Sprintf("%d %s %d는 연산할 수 없습니다.", leftOperand, opcode, rightOperand)
        } else if operation > 0 && operation < 7 {
                result = calc(leftOperand, rightOperand, opcode)
        }

        fmt.Println("결과: ", result)
}

func calc(left, right int64, opcode string) string {
        var result int64
        switch opcode {
        case PLUS:
                result = left + right
        case MINUS:
                result = left - right
        case MULTI:
                result = left * right
        case DIV:
                result = left / right
        case REMAINDER:
                result = left % right
        case SQUARE:
                result = pow(left, right)
        }
        return fmt.Sprintf("%d %s %d = %d", left, opcode, right, result)
}

func getOpcode(operation int) (opcode string) {
        switch operation {
        case PLUS_NO:
                opcode = PLUS
        case MINUS_NO:
                opcode = MINUS
        case MULTI_NO:
                opcode = MULTI
        case DIV_NO:
                opcode = DIV
        case REMAINDER_NO:
                opcode = REMAINDER
        case SQUARE_NO:
                opcode = SQUARE
        }
        return
}

func pow(base, cnt int64) int64 {
        var result int64 = 1
        for {
                result *= base
                cnt--
                if cnt == 0 {
                        return result
                }
        }
}

위와 같은데 사실 제곱 계산하는 로직은 이렇게 짜면 안 되지만  for문이 중요하기 때문에 아주 간단하게 코딩했다 ㅋ;;

 

다음 예제는 array나 slice를 리뷰하기 전 예습이라 생각하면 좋을 것 같다. 문자하나와 여러 문자열을 입력받아 하나의 문자열로 만들어주는 Join함수를 만들어 보겠다.

package main

import "fmt"

func main() {
        fmt.Println(join('_', "won", "yong", "yun", "golang", "node", "java", "ts", "php"))
}

func join(separator rune, inputs ...string) string {
        result := inputs[0]
        for _, input := range inputs[1:] {
                result = fmt.Sprintf("%s%c%s", result, separator, input)
        }

        return result
}

join 함수에서 사용한 for 접근할 수 있는 값, 요소 := range (array | map | slice) 형태이다. Golang에서 정말 많이 사용하는 형태이니 잘 기억해 두면 좋을 것 같다.

 

끄~~~읏!!!

예제코드

다음 주는 이번 주보다 꿀잼이니 예제를 만들어 리뷰해야겠다~

'Golang > Tucker' 카테고리의 다른 글

[묘공단] 4주차  (2) 2023.10.22
[묘공단] 3주차 (2)  (1) 2023.10.16
[묘공단] 3주차 (1)  (2) 2023.10.15
[묘공단] 2주차 (1)  (0) 2023.09.29
[묘공단] 1주차  (0) 2023.09.24