본문 바로가기
Golang/Tucker

[묘공단] 3주차 (2)

by 윤원용 2023. 10. 16.

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

 

16 챕터는 Golang을 하면서 궁금했던 package이다. 책에서는 기본적인?? 내용만 알려주고 있어서 본인의 궁금증이 해소되진 않아 이번 기회에 궁금했던 것 중 일부를 추가로 리뷰해 볼 예정이다.

 

17 챕터는 시험?? 같은 거라 문제를 공유한 후 답은 본인이 짠 코드로만 공유할 예정이다. 책에 내용이 궁금하면 책을 사보길 권한다.

 

더보기
  • 16 패키지
    • 16.1 패키지
    • 16.2 패키지 사용하기
    • 16.3 Go 모듈
    • 16.4 패키지명과 패키지 외부 공개 (pass)
    • 16.5 패키지 초기화 (pass)
  • 17 숫자 맞추기 게임 만들기
    • 17.1 해법
    • 17.2 사전지식
    • 17.3 step1 랜덤한 숫자 생성하기
    • 17.4 step2 숫자값 입력받기
    • 17.5 step3 숫자 맞추기 완성하기

 

16.1 패키지

책에서 설명하는 package는 Golang에서 코드를 묶는 가장 큰 단위라고 설명하고 있다. 

본인들이 작성한 프로그램을 실행시키기 위해서는 main package가 꼭 필요하고 Golang에서 기본으로 제공해 주는 package들 외 공개된 오픈소스들도 import 할 수 있다.

책에서 유용한 패키지 찾는 방법을 소개하고 있으니 책을 사서 읽어보시길 ㅎㅎ;;

 

16.2 패키지 사용하기

package를 사용하기 위해서는 import 예약어를 사용하는데 여러 package를 import 하기 위해 소괄호를 사용해서 import 할 수 있다. 

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Join([]string{
		"hello",
		" world",
		"!!!",
	}, ""))
}

위 코드는 fmt와 strings package를 import 하는 코드이다. 보통 라이브러리들을 갖고 와 사용하는 경우에는 이런 경우가 별로 없는데 우리가 만드는 코드에서는 패키지명이 겹칠 경우가 있을 수 있다. fmt, strings와 같은 패키지는 패키지명 앞에 경로가 없어서 상관없지만 보통은 경로가 있다 이때 사용되는 게 go mod init 할 때 명시한 경로 + 패키지까지의 경로이다. 밑에 이미지를 보면 version1과 version2만 다르고 경로가 똑같다. 사실 폴더를 어떻게 관리하냐에 따라 다르겠지만 여러 라이브러리들을 사용하면서 느낀 점은 경로 마지막에는 거의 패키지명과 같은 폴더를 사용하는 것 같다.

 이렇게 import 할 패키지명이 겹치는 경우에는 alias를 사용해서 import를 관리할 수 있으나 본인은 안 좋은 방법인 것 같다  생각한다. 가능하면 패지키는 고유한 명을 사용하는 것을 추천한다.

 

16.3 Go 모듈

Golang 1.16 버전부터 모듈 사용이 기본 설정으로 자리 잡았다. 앞서 간단하게 리뷰한 내용처럼 go mod init moduleName 형태의 명령어를 입력하면 go.mod 파일이 생성된다. go.mod 파일이 있는 폴더 위치부터가 하나의 모듈로써 사용할 수 있게 된다.  외부 패키지를 사용할 때도 go.mod 파일이 필요한데 사용할 라이브러리들을 직접 코딩할 수 있지만 보통은 두 가지 방법을 사용하는 것 같다.

 

그전에 이전 예제로 만든 LinkedList를 따로 빼서 모듈을 만든 후 다운로드할 수 있도록 다시 만든 후 두 가지 방법으로 사용해 보겠다. 

 

첫 번째로 작업해야 할 것은 github의 repository에서 root를 설정해 주는 것이다. 

이런 식으로 아무런 코드가 없더라도 repository root에 go.mod를 생성한다. 

 

두 번째로 작업해야 할 것은 이제 공유할 모듈을 작업한다.

 

세 번째로 repository root에 go get github.com/YunWonYong/Golang/datastruct 명령을 사용해 다른 곳에서 root를 통해 datastruct를 접근할 수 있게 만든다. (이거 안 하면 datastruct를 못 불러옴)

 

이제 됐다.

 

1. go get moduleUrl

본인 개인적으로 이 방법이 제일 편하다. 후에 소개할 두 번째 방법을 사용하다 보면 원하지 않는 버전으로 다운로드될 때도 있어서 그거 확인하는 게 더 귀찮은 것 같다. 

go get github.com/YunWonYong/Golang/datastruct

터미널에서 위처럼 명령을 입력하면

위 이미지처럼 추가했다고 나오고 go.mod 파일에 해당 내용이 추가된다. 그 후 프로젝트 탐색기를 보면 go.sum 파일도 생기는데 이 go.sum 파일은 다운로드한 패키지 위조 여부를 검사하기 위한 체크섬(checksum) 결과가 담겨있다.

 

이제 다운로드한 패키지를 사용하는 코드를 보면

이렇게 잘 불러와서 사용할 수 있다.

 

2. go mod tidy

이 명령어를 사용하면 go.mod를 기준으로 모든 *.go 파일에 import 하고 있는 파일들을 다운로드하여 go.mod와 go.sum에 기록하여 외부 모듈들을 사용할 수 있게 만들어준다.

이렇게 import 하는 외부 모듈을 못 찾고 있을 때 커맨드에 go mod tidy를 사용하면

해당 모듈도 다운로드하고 go get과 같이 go.mod 파일과 go.sum 파일 또한 갱신된다.

 

 

16.4 ~ 5

16.4는 많이 다뤘던 내용이라 pass!! 16.5는 본인 생각엔 남용하면 큰 피를 보기 때문에 모르는 게 낫다 판단해서 pass!!

 

17 숫자 맞추기 게임 만들기

게임을 간단하게 설명하면 랜덤 숫자 하나를 생성한 후 사용자에게 숫자를 입력받아 랜덤 한 숫자와 같으면 몇 회 만에 맞췄는지 출력하고 같지 않으면 힌트를 주어 맞출 때까지 진행하는 게임이다.

게임 규칙은 아래와 같다.

  1. 먼저 0~99 사이의 랜덤 숫자 하나를 생성한다.
  2. 사용자의 입력을 받기 위해 "숫자를 입력하세요."라고 출력하고 시도 횟수를 1 올린다.
    1. 사용자의 입력이 숫자가 아닌 경우 "숫자만 입력하세요."라고 출력한다.
  3. 사용자가 입력한 숫자와 생성한 랜덤 숫자가 같은지 확인한다.
    1. 입력한 숫자와 랜덤 숫자가 다르면
      1. 입력한 숫자가 랜덤 숫자보다 크면 "입력하신 숫자가 더 큽니다."를 출력하고 2번으로 돌아간다.
      2. 입력한 숫자가 랜덤 숫자보다 작으면 "입력하신 숫자가 더 작습니다."를 출력하고 2번으로 돌아간다.
    2. 입력한 숫자와 랜덤 숫자가 같으면
      1. "숫자를 맞췄습니다. 축하합니다. 시도한 횟수: [횟수]"를 출력하고 프로그램을 종료시킨다.

코드는 아래와 같다.

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"time"
)

const (
	RANDOM_NUMBER_GENERATE_BOUNDARY int = 100

	INPUT_NUMBER_MESSAGE         string = "숫자값을 입력하세요:"
	INPUT_NUMBER_ERROR_MESSAGE   string = "숫자만 입력하세요."
	INPUT_NUMBER_LESS_MESSAGE    string = "입력하신 숫자가 더 작습니다."
	INPUT_NUMBER_GREATER_MESSAGE string = "입력하신 숫자가 더 큽니다."
	CLEAR_MESSAGE                string = "축하합니다. 숫자를 맞추셨습니다. 시도횟수: %d번"
)

func main() {
	targetNum := getRandomNumber(RANDOM_NUMBER_GENERATE_BOUNDARY)
	inputCnt := 0
	msgNum := 1
	for msgNum != 3 {
		inputCnt++
		inputNum, err := inputNumber()
		if err != nil {
			continue
		}

		print(getNumberCheckState(targetNum, inputNum), inputCnt)
	}
}

func getRandomNumber(boundary int) int {
	seed := time.Now().UnixNano()
	randSource := rand.NewSource(seed)
	rand := rand.New(randSource)
	return rand.Intn(boundary)
}

func inputNumber() (int, error) {
	fmt.Print(INPUT_NUMBER_MESSAGE)
	inputNum := 0
	_, err := fmt.Scanf("%d\n", &inputNum)

	if err != nil {
		fmt.Println(INPUT_NUMBER_ERROR_MESSAGE)
		inputStreamFlush()
		return 0, err
	}
	return inputNum, nil
}

func inputStreamFlush() {
	bufio.NewReader(os.Stdin).ReadString('\n')
}

func getNumberCheckState(targetNumber, inputNumber int) int {
	if targetNumber < inputNumber {
		return 1
	} else if targetNumber > inputNumber {
		return 2
	}
	return 3
}

func print(messageNumber, inputNum int) {
	msg := ""
	switch messageNumber {
	case 1:
		msg = INPUT_NUMBER_LESS_MESSAGE
	case 2:
		msg = INPUT_NUMBER_GREATER_MESSAGE
	case 3:
		msg = fmt.Sprintf(CLEAR_MESSAGE, inputNum)
	}

	if len(msg) == 0 {
		panic(fmt.Sprintf("not supported messageNumber: %d", messageNumber))
	}

	fmt.Println(msg)
}

책과 다른 점은 random number 생성하는 부분과 코드뿐이다.  책을 읽으면서 아쉬웠던 점은 사용자가 숫자가 아닌 다른 것을 입력했을 때 입력 스트림을 비워줘야 하는 부분에 설명이 미흡한 부분인 것 같다.

 

방통대 과제도 하고 이것도 하느라 조금 힘들었는데 오늘로 과제는 끝나서 개꿀이당~ ㅋㅋ

다다음주는 본인이 발표해야 하기 때문에 이제 겁나 열심히 공부해야겠다 ㅎㅎ;;

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

[묘공단] 5주차 (1)  (1) 2023.10.29
[묘공단] 4주차  (2) 2023.10.22
[묘공단] 3주차 (1)  (2) 2023.10.15
[묘공단] 2주차 (2)  (1) 2023.10.09
[묘공단] 2주차 (1)  (0) 2023.09.29