프로그래밍 언어/Go

Go: Go 답게 코드 작성하기

Go 언어는 C언어를 일부 계승하였기 때문에 다른 언어와는 사용법이 상당히 달라서 자바와 같이 전통적인 객체지향적 프로그래밍에 익숙한 경우라면 적응하기 어려울 수도 있다. 특히 Go 에는 예외가 없고 에러로만 처리하는 것도 그렇지만, 에러의 처리 방식이 다소 호불호가 갈리는 방식이기 때문에 이를 단점으로 삼는 사람도 있는 것 같다. 에러에 대한 내 의견 또한 아래에서 언급해볼 것이다. Effective Go 을 읽어보면 더욱 좋겠지만, 그와 관련된 포스트는 이후에 이야기해보도록 하자.

 

https://gosudaweb.gitbooks.io/effective-go-in-korean/content/

 

README · Effective Go in Korean

 

gosudaweb.gitbooks.io

panic 은 피하고, error 를 확실하게 처리하기

Go 에서 컴파일 에러를 제외한 런타임 중 발생하는 에러를 패닉(Panic)이 발생했다고 하며 이는 치명적인 상태로써 발생하는 순간 어플리케이션이 중지된다. 따라서 이러한 패닉이 발생하는 것은 피해야 함은 물론, 여러 함수와 메서드를 사용했을 때 나타나는 에러에 대해서도 하나하나 처리를 해줄 필요가 있다.

 

Go: 에러와 패닉 (panic, recover)

 

Go: 에러와 패닉 (panic, recover)

예외가 없다고? 그렇다. Go 언어에는 예외(Exception)가 없다. 에러로 모든 것을 처리한다. error 빌트인 타입은 존재하긴 하지만, 그렇다고 타언어처럼 모든 에러에 대해 타입이 매핑되어 있는 것은

pronist.tistory.com

아래의 코드는 strconv.ParseBool() 함수를 통해 bool 타입으로 변경할 수 있는 형태의 string 타입의 값에 대해 처리를 시도한다. 값이 "true" 의 경우에는 올바르게 바뀔테지만, 값이 "10" 과 같이 숫자의 형태이거나 bool 타입으로 바꾸기 곤란한 상태로 바꾸려고 시도한다면 그것은 실패한다. Go 에서는 이러한 실패에 대해 error 값을 반환해주게 되는데, 이러한 에러 값을 잘 생략하지 않고 세세하게 처리를 해줄 필요가 있다.

package main

import (
	"fmt"
	"log"
	"strconv"
)

func main() {
	boolVar, err := strconv.ParseBool("true")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v", boolVar)
}

한편 이러한 처리 방식에 대해 error 를 반환하는 함수나 메서드를 사용할 때 늘 비슷한 방식으로 처리해야해서 코드가 중복된다는 점을 싫어하게 될 수도 있다. 헌데 가만히 생각해보면 다른 언어에서 try ~ catch 를 사용 할 때 정확하게 예외를 처리해주지 못하거나 코드에서 발생하는 여러가지 하위 계층 예외 클래스를 사용하기가 쉽지 않아 어쩔 수 없이 가장 최상위의 예외 클래스를 사용했던 경험이 있는데, Go 와 같은 처리 방식은 코드는 조금 지저분 해보일지라도 프로그램은 더 견고해질 수 있을지도 모른다.

정규 표현보다 strings 패키지 사용하기

Go 언어의 정규 표현 엔진은 기능을 많이 가지고 있지만, 가능하면 strings 패키지의 문자열 처리 함수들을 사용하는 것이 좋다. 정규 표현을 사용하는 것보다 strings 패키지의 함수들을 사용하는 것이 더 속도가 빠르기 때문이다. 정규 표현을 잘못하면 다른 스크립트 언어보다도 느린 속도가 될 수 있음을 주의하자.

거대한 구조체 피하기

Go 언어의 모토는 작은 부품을 사용하여 큰 프로그램을 만드는 것이다. 작은 노력들이 모여 큰 업적을 이루는 것과 비슷하다. Go 에서는 상속이라는 개념이 없는 대신 구조체 임베딩과 같은 개념을 사용한 위임이라는 것이 존재한다. 너무 많은 필드가 구조체에 정의되어 있으면 해당 구조체는 정체성을 잃거나 너무 많은 정보를 표현하려 들기 때문에 사용하기가 어려워 독립적인 구성하기 어려워질 것이다.

 

Go: 구조체 (메서드, 임베딩, 캡슐화)

 

Go: 구조체 (메서드, 임베딩, 캡슐화)

Go 에는 클래스가 없다. 개념적으로 객체라는 단어를 거의 사용하지 않는다. 다만 C언어처럼 구조체라는 존재가 있다. 구조체는 클래스와 유사하지만 전통적인 객체지향 프로그래밍의 형태보다

pronist.tistory.com

map 을 피하고 구조체 타입 정의하기

Go 의 map 타입은 키와 값을 가질 수 있는 연관 배열로 사용할 수 있다. 특히 키를 문자열로 사용하는 경우가 많아서 자바스크립트의 객체 리터럴처럼 사용하는 경우가 있는데, 이러한 것을 피하고 가급적이면 구조체 타입을 적극 활용하자. 구조체 타입을 사용하면 해당 타입이 무엇을 의미하는지 나타낼 수 있으며 IDE 를 사용함에 있어서도 도움을 받을 수 있는 부분이 많다.

package main

type Developer struct {
	Email string
	Company string
}

func main() {
	//d := map[string]string{
	//	"Email": "pronist@naver.com",
	//	"Company": "",
	//}
	d := Developer{
		Email: "pronist@naver.com",
		Company: "",
	}
}

reflect 많이 사용하기 않기

reflect 패키지를 사용하면 할 수 있는 일이 많기는 하지만, 너무 남발하게 되면 코드의 가독성만 저해될 뿐이다. 해당 패키지를 사용해야 할 때는 주로 다른 개발자가 사용해야 하는 라이브러리를 작성해야 할 때이다.

고루틴 남발하기 않기

Go 에서 동시성 지원을 위해 동시에 코루틴을 실행하는 것은 아주 쉽다. 그저 go 키워드를 써주면 그만이지만, 꼭 필요할 때가 아니라 남발하는 경우, 나중에 버그가 발생했을 때 유지보수하기가 상당히 어려워지며 버그의 지점도 찾기 어려워진다. 동시성을 다룰때는 여러모로 주의해야 하는데, sync.Mutex 를 사용하여 전통적인 메모리 동기화를 사용하거나 채널을 사용하여 고루틴을 블로킹을 하는 등 여러 작업이 필요하다. 데드락, 레이스 컨디션 등의 문제를 맞닥들이고 싶지 않다면 동시성 사용에 주의하자.

go vet, golint 등 Go 도구 활용하기

go vet 은 정적 분석 도구로써 고에 기본적으로 내장되어 있으며 이를 사용하면 버그를 일으킬만한 코드를 잡아준다. go 자체는 여러가지 도구가 탑재되어 있으나 이를 살펴보는 것이 좋다. 물론 고랜드 등 전용 IDE 를 사용하면 알아서 잡아주는 경우도 있긴 하지만, Go 의 경우에는 코딩 스타일이 제각각이지 않고 권장되는 코딩 스타일이 이미 정해져있기 때문에 동일한 코드 린트를 통해 코드 스타일을 일관적으로 유지할 수 있다. Go 의 코드를 포맷팅해주는 것에 대한 부분은 go fmt 도구이며 golint 를 사용하면 Go 답지 않은 코딩 스타일을 잡아준다.