ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Golang 찍어먹어보기
    Tech 2021. 3. 14. 12:42

    golang의 마스코트

    심심한 마음에 Go 언어를 한 번 찍어먹고자 합니다. 

     

    기본적으로 web backEnd 개발에 자주 쓰이는 언어로 각광받고 있고, 

    Google에서 개발 및 유지보수하고 있으니 안정성이 보장됩니다.

    또한, 현재 개발 환경에 많이 도입되고 있는 Docker와 Kubernetes의 기반 언어로 사용되는 만큼 꼭 한 번 써 볼 가치는 있다고 생각됩니다.

     

    장점으로 많이 언급되는 것은 경량 쓰레드를 구축하여, 멀티 쓰레드 기반임에도 가볍다는 점과, 컴파일 속도가 빠르다는 점이 있고, 단점으로는 함수형을 추구하는 만큼 객체 지향과는 멀어지는 감이 있습니다. 물론 이는 package 단위로 관리를 하여 문제없다고 보는 시선도 많습니다. 또한, 국내에서 사용하는 업체가 거의 없다는 것이 큰 단점이라고 볼 수 있을 거 같습니다.

     


    철저히 공식 홈페이지의 튜토리얼을 따라서 실행해본 내용을 요약정리한 내용입니다. 실제로 튜토리얼을 직접 수행하는 것이 더 많은 것을 배울 수 있습니다.

    golang.org/doc

     

    Documentation - The Go Programming Language

    Documentation The Go programming language is an open source project to make programmers more productive. Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and netwo

    golang.org


    Install

    설명이 필요할까라는 수준으로 설치가 매우 쉽다.

    글쓴이는 MAC을 사용하므로 pkg 파일을 다운로드하고,
    go version으로 설치 확인 완료하였다.

    참고 : https://golang.org/doc/install

    Tutorial: Getting started

    IDE

    공식 홈페이지에서는 VSCode와 GoLand, Vim을 추천하는데, 이미 VSCode 생태계에 익숙하기 때문에,
    VSCode를 설치하였고, 자동완성 기능같은 경우는 VSCode의 extensions market에서 Go를 추가해주면 쉽게 지원받을 수 있다.

    Go extension 설치 화면

    프로젝트 생성

    원하는 경로로 이동한 후에 프로젝트를 시작한다.

    $ mkdir helloworld
    $ cd helloworld
    
    $ go mod init example.com/hello

    하위 command는 프로젝트를 시작하기 위한 명령어이다.

    해당 명령어를 실행하면, 전체 프로젝트의 dependency를 표기하는 go.mod 파일을 생성한다. (node로 치면 package.json)

    해당 파일에는 해당 프로젝트 외부에서 사용하는 모듈에 대한 정보를 모두 기록하며, 기본적으로 repository에 대한 정보 역시 같이 표기한다. (go mod init example.com/hello에서 example.com/hello가 이에 해당한다.)  

     

    main 파일 생성

    hello.go라는 파일을 생성 후 다음과 같은 code를 입력한다.

    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("Hello, world")
    }
    • package [name] 은 여러 함수들을 한 데 모으는 역할을 하며, 같은 directory의 파일들 간에서 package 이름을 가지는 file끼리는 함수를 공유한다.
    • import "[name]" 는 해당 package 이외의 함수를 필요로 할 때, 이를 불러올 수 있는 방법이다. 여기서는 가장 기본적으로, text를 formatting 할 때 쓰이는 fmt를 import를 하였다.
    • 기본적으로 main함수를 찾아서 프로젝트를 실행시키기 때문에 main 함수가 기본 시작점이 된다. 그곳에 Hello, world라는 문장을 콘솔창에 출력하도록 하였다.

    실행

    $ go run .
    Hello, World!

    해당 directory에서 main을 가지는 file을 찾아서 실행시킨다.


    package 추가

    기본적으로 golang은 여러가지의 기본 package를 제공하지만, 필요로 되는 package는 pkg.go.dev에 추가적으로 확인할 수 있습니다.

    해당 package의 정보를 확인한 후, 이를 불러와서 우리의 package에 적용하는 것이 가능합니다.

     

    Home · pkg.go.dev

     

    pkg.go.dev

     

    package main
    
    import "fmt"
    
    import "rsc.io/quote"
    
    func main() {
    	fmt.Println(quote.Go())
    }

    다음과 같이 rsc.io/quote를 추가하고자 할 때, 다음과 같이 import를 통해서 모듈을 추가해줄 수 있으며, 이 상태에서 특정 command를 실시하면, 해당 module에 대한 정보가 go.mod 파일에 기록되며, 해당 module의 보안을 위한 암호화 정보가 go.sum에 저장된다.(없으면 생성됨)

    $ go mod tidy
    go: finding module for package rsc.io/quote
    go: found rsc.io/quote in rsc.io/quote v1.5.2
    
    $ go run .
    Don't communicate by sharing memory, share memory by communicating.

    이를 통해서 프로젝트에 외부 모듈을 추가할 수 있다.

    More Default Tutorial

    1. Create a module -- function을 만들고 호출하는 과정을 수행
    2. Call your code from another module -- 외부 모듈을 호출하는 과정을 수행
    3. Return and handle an error -- 간단한 error handling을 직접 수행
    4. Return a random greeting -- 간단한 모듈을 사용하여 기존 코드 업데이트
    5. Return greetings for multiple people -- map형태로 데이터 저장
    6. Add a test -- built-in testing 수행
    7. Compile and install the application -- local에서 코드를 build하고 install 하는 과정 수행

     

    모듈 생성(Create a module)

    앞으로의 구현을 위해서 새로운 프로젝트 폴더를 생성합니다.

    $ mkdir greetings
    $ cd greetings
    
    $ go mod init example.com/greetings

    그리고, greetings.go라는 파일을 생성하고, 다음 내용을 작성합니다.

    package greetings
    
    import "fmt"
    
    // Hello returns a greeting for the named person.
    func Hello(name string) string {
        // Return a greeting that embeds the name in a message.
        message := fmt.Sprintf("Hi, %v. Welcome!", name)
        return message
    }

    변수를 할당하고, 데이터를 삽입하는 과정은 기본적으로 다음과 같은 format을 따른다.

    golang에서는 데이터를 대입하는 연산으로 :=을 사용한다.

    var message string
    message = fmt.Sprintf("Hi, %v. Welcome!", name)

    하지만, 이미 데이터의 형태가 우항의 값에 의해서 정해지는 경우는 자동으로 이를 지정해주는 것이 가능하다.

     

    기본적인 함수 구현은 하당 그림을 통해서 볼 수 있다.

    여기서 주의할 점은 외부로 export할 함수는 표기법이 첫 글자를 대문자로 표기한다는 것이다.
    또한, 인자와 리턴값의 type을 다음과 같이 정의할 수 있다.

    golang 기본 함수 구현

    외부 모듈에서 함수 호출하기(Call your code from another module)

    greetings 프로젝트 폴더에서 나와서 별도의 hello 폴더를 만듭니다.

    ex)

    <home>/
     |-- greetings/
     |-- hello/

    hello 폴더에 들어가서 go project를 시작합니다.

    $ cd hello
    $ go mod init example.com/hello

    hello.go라는 파일을 해당 파일 안에 추가하고 다음 내용을 작성합니다.

    package main
    
    import (
        "fmt"
    
        "example.com/greetings"
    )
    
    func main() {
        // Get a greeting message and print it.
        message := greetings.Hello("Gladys")
        fmt.Println(message)
    }

    여기서 이전에 구현하였던 greetings를 사용하기 위해서 import를 수행하는 것을 볼 수 있다. 하지만, 해당 url에는 우리가 작성한 함수가 있을 리가 만무하다. 

    Go에서는 이를 mapping해줄 수 있는 command가 별도로 존재한다. example.com/greetings가 greetings폴더에 있는 프로젝트라는 것을 알려주기 위해서는 다음과 같은 명령어를 수행해야 한다.

    $ go mod edit -replace=example.com/greetings=../greetings

    이를 수행하게 되면, go.mod 파일은 다음과 같은 형태로 바뀌게 된다.

    module example.com/hello
    
    go 1.16
    
    replace example.com/greetings => ../greetings

    마지막으로, 해당 프로젝트와 version 정보를 sync 해주는 과정을 거치는데 이 과정에서 다음과 같은 command를 수행하면 된다.

    $ go mod tidy
    go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000

    이를 수행하게 되면, go.mod 파일은 최종적으로 다음과 같은 형태가 되게 된다.

    module example.com/hello
    
    go 1.16
    
    replace example.com/greetings => ../greetings
    
    require example.com/greetings v0.0.0-00010101000000-000000000000

    마지막으로, hello project를 실행해보면 정상적으로 작동하는 것을 확인할 수 있다.

    $ go run .
    Hi, Gladys. Welcome!

     

    리턴 값과 에러 처리 (Return and handle an error)

    기존에 만들었던, greetings/greetings.go 파일에서는 이름이 없을 경우에도 그냥 hello를 출력합니다. 따라서, 이 경우의 에러 처리를 위한 코드를 추가할 것입니다.

    package greetings
    
    import (
        "errors"
        "fmt"
    )
    
    // Hello returns a greeting for the named person.
    func Hello(name string) (string, error) {
        // If no name was given, return an error with a message.
        if name == "" {
            return "", errors.New("empty name")
        }
    
        // If a name was received, return a value that embeds the name
        // in a greeting message.
        message := fmt.Sprintf("Hi, %v. Welcome!", name)
        return message, nil
    }
    • Go에 기본으로 포함되는 errors package에서 error를 생성하는 함수를 불러올 수 있습니다.
    • 함수의 return 타입에 string과 error를 동시에 기입하여, 이를 처리하는 것을 호출단(caller)에서 수행할 수 있도록 합니다.
    • 여기서 nil은 사전적인 단어로는 nothing을 의미하며, null과 비슷한 의미로 사용할 수 있습니다. (nil은 zero value이고, 이는 명시적인 초기값을 할당하지 않고 변수를 만들었을 때 해당 변수가 갖게 되는 값을 의미합니다.)

    그 후에, 호출단인 hello/hello.go 파일을 다음과 같이 변경합니다.

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Set properties of the predefined Logger, including
        // the log entry prefix and a flag to disable printing
        // the time, source file, and line number.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // Request a greeting message.
        message, err := greetings.Hello("")
        // If an error was returned, print it to the console and
        // exit the program.
        if err != nil {
            log.Fatal(err)
        }
    
        // If no error was returned, print the returned message
        // to the console.
        fmt.Println(message)
    }
    • log package는 console창에 log를 출력하는 것에 대한 기능을 포함하고 있으며, 여기서는 prefix를 지정하고, log에 시간과 같은 설정을 제거하도록 설정하였다. 그 후 에러가 발생하면, 이 에러에 대한 내용을 console에 logging 하며 프로그램을 정지하도록 하였다. (Fatal)

    실제 실행을 시켜보면, 알맞지 않게 입력된 인자로 인해 에러가 발생하는 것을 볼 수 있다.

    $ go run .
    greetings: empty name
    exit status 1

    랜덤 이용하기 (Return a random greeting)

    인사를 매번 동일하게 하는 것이 지겨웠는지 일정 패턴 속에서 랜덤하게 인사말을 하도록 바꾸기로 했다. 따라서, 기존 greetings/greetings.go 파일을 다음과 같이 변경한다.

    package greetings
    
    import (
        "errors"
        "fmt"
        "math/rand"
        "time"
    )
    
    // Hello returns a greeting for the named person.
    func Hello(name string) (string, error) {
        // If no name was given, return an error with a message.
        if name == "" {
            return name, errors.New("empty name")
        }
        // Create a message using a random format.
        message := fmt.Sprintf(randomFormat(), name)
        return message, nil
    }
    
    // init sets initial values for variables used in the function.
    func init() {
        rand.Seed(time.Now().UnixNano())
    }
    
    // randomFormat returns one of a set of greeting messages. The returned
    // message is selected at random.
    func randomFormat() string {
        // A slice of message formats.
        formats := []string{
            "Hi, %v. Welcome!",
            "Great to see you, %v!",
            "Hail, %v! Well met!",
        }
    
        // Return a randomly selected message format by specifying
        // a random index for the slice of formats.
        return formats[rand.Intn(len(formats))]
    }
    • randomFormat 함수는 해당 package 내에서만 사용되는 것이므로 함수명이 소문자로 시작하는 것을 볼 수 있다.
    • 또한, 배열을 생성할 때에는 타입 앞에 []를 표기하여 배열임을 표기한다.
    • 여기서 math/rand package는 랜덤한 수를 고를 수 있도록 한다.
    • init함수는 프로그램이 실행되고, global variables가 초기화된 후에 자동적으로 실행된다. 따라서, 이 안에 seed를 수행(데이터 초기화)하는 과정을 하는 것이 일반적이며, 해당 예에서는 rand를 현재 시간을 기점으로 하여, seed를 배정한 것을 볼 수 있다.

    그 후, 호출 부에서 다음과 같이 변경하면, 실행 시마다 변경되는 인사말을 볼 수 있다.

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Set properties of the predefined Logger, including
        // the log entry prefix and a flag to disable printing
        // the time, source file, and line number.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // Request a greeting message.
        message, err := greetings.Hello("Gladys")
        // If an error was returned, print it to the console and
        // exit the program.
        if err != nil {
            log.Fatal(err)
        }
    
        // If no error was returned, print the returned message
        // to the console.
        fmt.Println(message)
    }

     

    Map을 이용하여 여러 개의 return값 표현하기(Return greetings for multiple people)

    한 번에 여러 사람의 이름을 받아서, 각 각 사람들에게 인사말을 해주어야 하는 경우를 생각해보자. 이는 기존 함수를 여러 번 호출 한느 것으로 대체될 수 있다. 하지만, 이 또한 모듈로 생성한다면, 편리하게 이용이 가능할 것이다. 따라서, 이러한 역할을 하는 Hellos라는 함수를 greetings/greetings.go에 생성해보자.

     

    package greetings
    
    import (
        "errors"
        "fmt"
        "math/rand"
        "time"
    )
    
    // Hello returns a greeting for the named person.
    func Hello(name string) (string, error) {
        // If no name was given, return an error with a message.
        if name == "" {
            return name, errors.New("empty name")
        }
        // Create a message using a random format.
        message := fmt.Sprintf(randomFormat(), name)
        return message, nil
    }
    
    // Hellos returns a map that associates each of the named people
    // with a greeting message.
    func Hellos(names []string) (map[string]string, error) {
        // A map to associate names with messages.
        messages := make(map[string]string)
        // Loop through the received slice of names, calling
        // the Hello function to get a message for each name.
        for _, name := range names {
            message, err := Hello(name)
            if err != nil {
                return nil, err
            }
            // In the map, associate the retrieved message with
            // the name.
            messages[name] = message
        }
        return messages, nil
    }
    
    // Init sets initial values for variables used in the function.
    func init() {
        rand.Seed(time.Now().UnixNano())
    }
    
    // randomFormat returns one of a set of greeting messages. The returned
    // message is selected at random.
    func randomFormat() string {
        // A slice of message formats.
        formats := []string{
            "Hi, %v. Welcome!",
            "Great to see you, %v!",
            "Hail, %v! Well met!",
        }
    
        // Return one of the message formats selected at random.
        return formats[rand.Intn(len(formats))]
    }
    

     

    • make를 통해서 map이라는 데이터형을 생성하였고, 이는 key값과 value값을 mapping하여 저장하는 형태이다. (위에서는 string key에 string value를 사용한다고 명시하였다.)
    • Loop문을 생성하기 위해서 for함수를 사용하였고, 기본적으로 for문의 형태는 C++의 형태 또는 python과 같은 range와 같은 형태를 사용할 수 있다. (물론 요즘 C++도 range 같은 기능을 지원한다.) 여기서는 range문을 이용하여 입력으로 들어온 names를 순회하며 name을 받아와서 Hello를 호출하는 것을 볼 수 있다.

    이제, 이를 호출하는 부분인 hello/hello.go에서 해당 부분을 변경하여 여러 개를 한 번에 출력할 수 있다.

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Set properties of the predefined Logger, including
        // the log entry prefix and a flag to disable printing
        // the time, source file, and line number.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // A slice of names.
        names := []string{"Gladys", "Samantha", "Darrin"}
    
        // Request greeting messages for the names.
        messages, err := greetings.Hellos(names)
        if err != nil {
            log.Fatal(err)
        }
        // If no error was returned, print the returned map of
        // messages to the console.
        fmt.Println(messages)
    }

    이를 실행시키면 map형태의 데이터가 나오는 것을 볼 수 있고 이 또한 loop문을 이용하여 하나씩 출력하도록 해볼 수도 있다.

    $ go run .
    map[Darrin:Hail, Darrin! Well met! Gladys:Hi, Gladys. Welcome! Samantha:Hail, Samantha! Well met!]

     

    테스트 수행하기 (Add a test)

    다양한 언어에서 test를 수행하기 위한 tool들이 존재하며, 이는 해당 코드의 usecase에 알맞은 실행 예시와 안전한 실행을 보장하는 역할을 한다.

    기본적으로 Go는 이에 대한 내용을 기본적으로 포함하고 있으면 이를 별도의 설치 없이 사용하는 것이 가능하다.

    greetings 폴더 안에 greetings_test.go를 생성한다. 그러면, Go는 _test.go라는 파일명을 인식하여 test 파일이라는 것을 알게 된다.

    그 후 해당 파일에 해당 내용을 작성한다.

    package greetings
    
    import (
        "testing"
        "regexp"
    )
    
    // TestHelloName calls greetings.Hello with a name, checking
    // for a valid return value.
    func TestHelloName(t *testing.T) {
        name := "Gladys"
        want := regexp.MustCompile(`\b`+name+`\b`)
        msg, err := Hello("Gladys")
        if !want.MatchString(msg) || err != nil {
            t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
        }
    }
    
    // TestHelloEmpty calls greetings.Hello with an empty string,
    // checking for an error.
    func TestHelloEmpty(t *testing.T) {
        msg, err := Hello("")
        if msg != "" || err == nil {
            t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
        }
    }
    • 해당 예시에서는 성공하는 경우와 실패하는 두 개의 경우의 수를 모두 테스트 하는 함수를 작성하였다.
    • 기본적으로 TestName이라는 형태로 함수를 작성하며, testing.T의 포인터를 인자로 포함한다. 이를 통해서 우리는 reporting과 logging을 수행할 수 있다.
    •  여기서 regexp는 정규 표현식과 관련된 기능을 수행할 수 있도록 합니다.

    이제 여기서 해당하는 test 코드를 실행할 수 있습니다. 

    // 해당 test함수들이 모두 성공했는지 여부만 확인하고 싶은 경우
    $ go test
    
    // 각 test함수에 대한 결과를 보고 싶은 경우
    $ go test -v

    만약, 코드를 바꾸어서 해당 test를 통과하지 못한다면, 실패를 출력하고, 그렇지 않다면, 성공하는 것을 볼 수 있습니다.

     

     

    프로젝트의 배포와 설치(Compile and install the application)

    이제 우리가 만든 프로그램을 배포할 것입니다. 이를 하나의 실행파일로 만드는 과정은 hello 폴더 안에서 다음 명령어를 실행하면 됩니다.

    $ go build

    이렇게 되면 실행 환경에 따라 알맞은 실행파일이 만들어집니다.

     

    그리고, go install을 통해서 생성된 실행파일을 GOBIN 파일로 이동시킬 수 있습니다.

    우리는 이를 이용해서 전역에서 실행할 수 있는 프로그램을 만들 수 있습니다. 

    일단 기본적으로 정의된 GOBIN 경로를 지정합니다. (대게 설치 시에 /Users/[user이름]/go/bin)

    환경 변수에 해당 파일에 대한 경로를 추가함으로써 해당 이름을 알려주는 것이지요.

    // On Linux or Mac, run the following command:
    $ export PATH=$PATH:/path/to/your/install/directory
    // On Windows, run the following command:
    $ set PATH=%PATH%;C:\path\to\your\install\directory
    
    
    // example
    $ export PATH=$PATH:/Users/[user이름]/go/bin

    이렇게 하면 hello 프로그램을 어느 위치에 폴더에서도 실행이 가능합니다.

    $ hello
    map[Darrin:Hail, Darrin! Well met! Gladys:Great to see you, Gladys! Samantha:Hail, Samantha! Well met!]

     

     

    출처

    golang.org

     

    The Go Programming Language

    Download Go Binary distributions available for Linux, macOS, Windows, and more. // You can edit this code! // Click here and start typing. package main import "fmt" func main() { fmt.Println("Hello, 世界") } Hello, World! Conway's Game of Life Fibonacci

    golang.org

    'Tech' 카테고리의 다른 글

    Go with GraphQL  (0) 2021.04.04
    chrome 확장 앱  (0) 2021.04.01
    RDF Turtle  (0) 2021.03.23
    A Tour of Go  (0) 2021.03.16
    Go를 이용한 web application 작성  (0) 2021.03.14

    댓글

Copyright © euidong