스터디📖/Go

쉽고 빠른 Go 시작하기 - #2 BANK & DICTIONARY PROJECTS

호프 2022. 4. 1. 11:52

2.0 Account + NewAccoun

지난번에 했던 실습 코드는 일단 주석처리를 하던지 지우고, 새로운 프로젝트 시작.
accounts라는 폴더를 만들고 accounts.go 라는 메인 파일을 만든다. 여기서는 컴파일을 진행하지 않을 것이기 때문에 이름이 main.go 가 아니더라도 상관 없다. 여기서 account라는 struct를 선언하고 다시 main.go에서 그 struct를 생성할 것이다.

가장 간단하고 직관적으로 생각한 방법은 accounts.go 안의 account struct와 그 안의 owner, balance 같은 속성을 모두 대문자로 시작하도록 작성해서 public으로 선언하는 것이다. 하지만 이렇게 하면 누구나 접근할 수 있어 보안적인 측면에서 좋지 않기때문에 constructor 함수를 만들어 사용하도록 한다. 이때 만드는 constructor 함수는 당연히 외부에서 접근이 가능해야 하므로 대문자로 선언한다.

// /accounts/accounts.go
package accounts //컴파일 안할거라서 이게 메인

// Account struct
type Account struct {
    owner   string
    balance int
} // 대문자로 작성해야 다른 곳에서 접근 가능.

// NewAccount creates Account
func NewAccount(owner string) *Account {
    account := Account{owner: owner, balance: 0}
    return &account // 복사본을 리턴하는 것이 아니라 실제 우리가 만든 memory address 리턴
}

//-------------------------------------------------------------------------------------------
// main.go
package main

import (
    "fmt"
    "github.com/Yoon-Suji/learngo/accounts"
)

func main() {
    account := accounts.NewAccount("suji")
    fmt.Println(account)
}

2.1 Methods part One

methods를 선언하는 방법은 func와 func 이름 사이에 receiver를 추가하는 것이다.
account의 balance를 증가시키는 Deposit 메소드를 다음과 같이 선언해보자.

// Deposit x amount on your account
func (a Account /*receiver*/) Deposit(amount int) {
    a.balance += amount
}

하지만 main 에서 해당 함수를 실행시키고 다시 account를 출력해보면 balance가 변하지 않은 것을 확인할 수 있다. 이는 해당 메소드가 account의 복사본을 만들고 그 복사본을 증가시키기 때문이다.

2.2 Methods part Two

그래서 상태를 변경하고 싶을 때는 다음과 같이 pointer receiver를 사용해야 한다.

// Deposit x amount on your account
func (a *Account /*receiver*/) Deposit(amount int) {
    a.balance += amount
}

error handling

go 에는 exception이나 try-catch 가 없다. 직접 error를 리턴하고 핸들링해주어야 한다.
balance에서 돈을 빼는 Withdraw method를 선언해보자. 만약 내 balance가 출금하고자 하는 돈보다 적은 경우에는 출금을 하지 못하게 error를 발생시켜야 할 것이다.

var errNoMoney = errors.New("Can't withdraw you are ppoor")

// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
    if a.balance < amount {
        return errNoMoney
    }
    a.balance -= amount
    return nil
}

위와 같이 error를 리턴타입에 적고, err가 발생하면 return error를 하고 발생하지 않는 경우에는 return nil을 한다. 여기서 nil은 null이나 None 같은 의미라고 생각하면 된다. error variable을 만들어서 사용하는 편이 더 가독성이 좋다고 한다.
그러면 이제 실행하는 쪽에서 error handling을 해야 한다.

func main() {
    account := accounts.NewAccount("suji")
    account.Deposit(10)
    err := account.Withdraw(20)
    if err != nil {
        log.Fatalln(err) // program 종료 해줌
    }
    fmt.Println(account.Balance())
}

2.3 Finishing Up

struct를 출력할 때 Go 에서 내부적으로 호출하는 String 메소드가 있는데 이를 사용해서 원하는 형식으로 출력되도록 할 수 있다.

// Go가 내부적으로 호출하는 메소드를 사용
func (a Account) String() string {
    return fmt.Sprint(a.Owner(), "'s account. \nBalance: ", a.Balance())
}

fmt.Println(accuont)

최종 코드

아니 뭐.. 계속 main.go 다 지우고.. 코드를 안 남겨놓네.. 여기에라도 남겨야지..

/accounts/accounts.go

package accounts //컴파일 안할거라서 이게 메인

import (
    "errors" // Account struct
    "fmt"
)

type Account struct {
    owner   string
    balance int
} // 대문자로 작성해야 다른 곳에서 접근 가능.

// NewAccount creates Account
func NewAccount(owner string) *Account {
    account := Account{owner: owner, balance: 0}
    return &account // 복사본을 리턴하는 것이 아니라 실제 우리가 만든 memory address 리턴
}

// Deposit x amount on your account
func (a *Account /*receiver*/) Deposit(amount int) {
    a.balance += amount
}

var errNoMoney = errors.New("Can't withdraw you are ppoor")

// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
    if a.balance < amount {
        return errNoMoney
    }
    a.balance -= amount
    return nil
}

// ChangeOwner of the account
func (a *Account) ChangeOwner(newOwner string) {
    a.owner = newOwner
}

// Balance of your account
func (a Account) Balance() int {
    return a.balance
}

// Owner of your account
func (a Account) Owner() string {
    return a.owner
}

// Go가 내부적으로 호출하는 메소드를 사용
func (a Account) String() string {
    return fmt.Sprint(a.Owner(), "'s account. \nBalance: ", a.Balance())
}

main.go

package main

import (
    "fmt"

    "github.com/Yoon-Suji/learngo/accounts"
)

func main() {
    account := accounts.NewAccount("suji")
    account.Deposit(10)
    err := account.Withdraw(20)
    if err != nil {
        //log.Fatalln(err) // program 종료 해줌
        fmt.Println(err)
    }
    fmt.Println(account)
}

2.4 Dictionary part One

이번에는 struct가 아닌 map type을 사용해 볼 것이다.
Go에서는 type에 alias를 붙여서 다음과 같이 사용할 수 있다.

type Money int
Money(1)

우리는 map[string]string 타입의 Dictionary라는 타입을 선언해서 사용할 것이다.
또한 type은 메소드를 가질 수 있기 때문에 타입 안에서 메소드로 Search 를 구현해서 사용해보자.

// /mydict/mydict.go

// Dictionary type
type Dictionary map[string]string //Dictionary == alias

// types can get method

var errNotFound = errors.New("Not Found")

// Search for a word
func (d Dictionary) Search(word string) (string, error) {
    value, exits := d[word]
    if exits {
        return value, nil
    }
    return "", errNotFound
}

// -----------------------------------------------------------
// main.go
func main() {
    dictionary := mydict.Dictionary{"first": "First word"}
    definition, err := dictionary.Search("first")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(definition)
    }
}

2.5 Add Method

다음은 Add Method를 구현해보자.

var errWordExists = errors.New("That word already exists")

// Add a word to the dictionary
func (d Dictionary) Add(word, def string) error {
    _, err := d.Search(word)
    // if err == errNotFound {
    //     d[word] = def
    // } else if err == nil {
    //     return errWordExists
    // }
    // return nil
    switch err {
    case errNotFound:
        d[word] = def
    case nil:
        return errWordExists
    }
    return nil
}

2.6 Update Delete

마지막으로 Update와 Delete 메소드도 만들어보자.

var (
    errNotFound   = errors.New("Not Found")
    errWordExists = errors.New("That word already exists")
    errCantUpdate = errors.New("Cant update non-existing word")
)

// Update
func (d Dictionary) Update(word, definition string) error {
    _, err := d.Search(word)
    switch err {
    case nil:
        d[word] = definition
    case errNotFound:
        return errCantUpdate
    }
    return nil
}

// Delete a word
func (d Dictionary) Delete(word string) {
    delete(d, word)
}