package banking
type Account struct {
Owner string
Balnace int
}
Account struct를 생성했다. 여기서 go의 접근 제어를 확인할 수 있는데. 만약 Account 대신 account를 쓴다면 이 struct는 외부에서는 접근할 수 없는 struct가 된다. go는 소문자와 대문자로 접근을 제어할 수 있기 때문인데, 그래서 Owner와 Balance 또한 대문자로 시작하게 작성하여 외부에서 접근이 가능하도록 제어해주어야한다!
또한 package가 baning으로 변경되었는데 해당 폴더 구조도 banking 폴더 내에 존재하도록 수정해주어야한다.
package main
import (
"fmt"
"github.com/project/helloworld/banking"
)
func main() {
account := banking.Account{Owner: "juno", Balnace: 1000}
fmt.Println(account)
}
그리고 print를 해보면 account를 확인해볼 수 있다.
하지만 이 코드에는 큰 문제가 있는데 모두 public으로 열려 있는 코드라는 것이다. 다른 언어를 사용해본 개발자라면 모두 접근이 가능하게 열어두는 것은 아주 큰 문제를 야기할 수 있다는 것을 알수 있다. 이 문제를 이제 해결해보자
go에는 생성자가 없다. 그렇기 때문에 우리가 직접 만들어주어야하는데
package accounts
type account struct {
owner string
balnace int
}
func NewAccount(owner string) *account {
account := account{owner: owner, balnace: 0}
return &account
}
다음과 같이 기존의 접근이 가능하던 Account 대신 account로 접근이 불가능하게 만들고 NewAccount
라는 함수를 접근할 수 있도록 생성하여 account를 return하도록 작성한다. 여기서 함수명은 New
를 붙이는게 암묵적인 약속인것 같다.
여기서 NewAccount 함수는
*account
를 반환하도록 되어 있는데 이 이유는 생성된 account를 복사해서 반환하여 메모리를 낭비하는 것이 아닌 함수 내에서 생성된 account 자체의 주소를 반환함으로써 그대로 반환하여 사용할 수 있도록 하는 것이기 때문이다.
package main
import (
"fmt"
"github.com/project/helloworld/accounts"
)
func main() {
account := accounts.NewAccount("juno")
fmt.Println(account)
}
그럼 다음과 같이 NewAccount라는 함수를 생성자처럼 사용하여
다음과 같이 결과를 확인할 수 있다.
package accounts
import "fmt"
type account struct {
owner string
balnace int
}
// create account
func NewAccount(owner string) *account {
account := account{owner: owner, balnace: 0}
return &account
}
func (a account) Deposit(balnace int) {
fmt.Println("call Deposit")
a.balnace += balnace
}
func (a account) Balance() int {
return a.balnace
}
여기서 go의 method라는 개념이 나온다. java에서 사용되던 method와는 다른 개념같은 느낌인데 기존에 사용하던 func가 일반적으로 함수로 사용되는 느낌이면 method는 func와 거의 동일하게 작성되지만 func 타입 함수명() 반환
순서로 작성되어 타입이 receiver로써 type을 선언하여 마치 struct 내부에 작성된 func처럼 작동할 수 있도록 만들어주는 함수와 같았다.
package main
import (
"fmt"
"github.com/project/helloworld/accounts"
)
func main() {
account := accounts.NewAccount("juno")
account.Deposit(10)
fmt.Println(account.Balance())
}
이렇게 작성하여 account의 입금된 금액을 확인해보면
Deposit은 실행되었지만 입금된 금액은 0원으로 나온다...
그 부분을 고치려면
package accounts
import "fmt"
type account struct {
owner string
balnace int
}
// create account
func NewAccount(owner string) *account {
account := account{owner: owner, balnace: 0}
return &account
}
func (a *account) Deposit(balnace int) {
fmt.Println("call Deposit")
a.balnace += balnace
}
func (a account) Balance() int {
return a.balnace
}
다음과 같이 Deposit의 리시버 부분에 *
를 붙여줘야하는데 그 이유는 account 객체를 전달받을 때 go는 무조건 복사해버린다. 그렇기 때문에 복사된 객체를 생성하여 값을 더해줘도 같은 account가 아닌 다른 주소에 저장된 account에 값을 더하고 종료되기 때문에 전혀 반영되지 않았던 것이다. 위 코드로 수정한 뒤 실행하면
정상적으로 반영된 것을 확인할 수 있다.
go는 무조건 복사본을 만든다는 점을 잊지말자!
package accounts
type account struct {
owner string
balnace int
}
// create account
func NewAccount(owner string) *account {
account := account{owner: owner, balnace: 0}
return &account
}
// account deposit
func (a *account) Deposit(balnace int) {
a.balnace = balnace
}
func (a *account) Withdraw(amount int) {
a.balnace -= amount
}
// get balance
func (a account) Balance() int {
return a.balnace
}
Withdraw 함수를 정의하여 출금을 만들어보자.
package main
import (
"fmt"
"github.com/project/helloworld/accounts"
)
func main() {
account := accounts.NewAccount("juno")
account.Deposit(10)
account.Withdraw(20)
fmt.Println(account.Balance())
}
출력해보면
-10이 찍혀나오는 것을 확인할 수 있다. 계좌에 -10이 나오는 것은 에러이다. 이를 다른 언어에서는 try catch
로 해결할 수 있지만 go에는 try catch
가 없다. 이제 에러를 핸들링해보자.
// account withdraw
func (a *account) Withdraw(amount int) error {
if a.balnace < amount {
return errors.New("Can not withdraw!")
}
a.balnace -= amount
return nil
}
이전에 작성했던 Withdraw 코드에 다음과 같이 에러를 추가해주었다.
다음과 같이 작성해주고 실제 코드를 실행하면
error가 발생하며 에러 이후의 코드가 실행되지 않는다. 그런데 나는 java 개발자여서 그런가 exception이 터져야하는데 터지지 않고 이렇게만 끝나니 뭔가 허전하다... 이제 error를 체크해보자.
error를 체크하는 방법은 우리가 위에서 작성한 코드에 error가 발생하지 않으면 nil을 반환하도록 작성해두었다. 이 값을 가지고 체크해볼 수 있다.
package main
import (
"fmt"
"github.com/project/helloworld/accounts"
)
func main() {
account := accounts.NewAccount("juno")
account.Deposit(10)
err := account.Withdraw(20)
if err != nil {
fmt.Println(err)
}
fmt.Println(account.Balance())
}
다음과 같이 err값을 반환받아서 nill인지 체크해주는 코드를 작성해서 체크하는 것이다...
그럼 다음과 같이 error가 발생한 부분을 체크할 수 있다.
기존의 try catch
를 통해 에러를 잡아내다가 if로 error를 체크하려니 뭔가 허전하지만 go는 이렇게 해야한다. 앞으로는 go에서 다음과 같이 에러 핸들링을 할것이기 때문에 익숙해져보자...!
그리고 좀 더 좋은 코드를 작성하는 방법으로는
package accounts
import "errors"
type account struct {
owner string
balnace int
}
var errNoMoney = errors.New("Can not withdraw!")
// create account
func NewAccount(owner string) *account {
account := account{owner: owner, balnace: 0}
return &account
}
// account deposit
func (a *account) Deposit(balnace int) {
a.balnace = balnace
}
// account withdraw
func (a *account) Withdraw(amount int) error {
if a.balnace < amount {
return errNoMoney
}
a.balnace -= amount
return nil
}
// get balance
func (a account) Balance() int {
return a.balnace
}
다음과 같이 에러부분을 따로 작성하는 것이 좀더 클린하게 코드를 작성할 수 있다.