본문 바로가기
Programming/Go

자바 개발자의 고 적응기

by TinKerBellBass 2022. 4. 3.
728x90
반응형

주로 자바로 개발을 해 오다 이번에 이직한 회사에서 고로 개발하게 되어서
고를 공부하며 느낀 점을 기록해 보려고 한다.
참고로 나는 고퍼도 아니며 자바에 환장한 사람도 아니며
고를 증오하지도 자바를 증오하지도 않는 그냥 평범한 개발자임을 밝혀둔다.
뭐든지 월급을 벌게 해주는 언어는 좋은 언어이다.

고의 철학

고를 사용하면서 느낀 첫인상은 Simple is best! 였다.
군더더기 없이 깔끔한 언어 스펙이 장점이자 단점으로 다가왔다.
다른 언어를 하나라도 익힌 개발자라면 책 한 권 정도 읽으면
코딩할 때 언어 스펙에 관해 일일이 찾아보지 않아도 될 정도로 심플하다.
언어 차원에서 지원해 줬으면 하는 부분도 있었으나 굳이 없어도 크게 불편한 점은 없었다.
고에서 뜨거운 감자였던 제네릭이 들어왔다. 다음은 열거형(enum)이 들어 올려나??
설마 자바 컬렉션 프레임워크의 스트림 같은 것은 들어올지 않겠지? 설마..
그렇게 된다면 고의 매력이 확 떨어질 것 같은데.
뭔가 고스러운 로우레벨 언어의 냄새는 없어지지 않았으면 좋겠다.

암묵적 형변환

고는 암묵적 형변환을 허용하지 않는다.
암묵적 형변환의 허용으로 인해 생기는 복잡하다면 복잡할 수 있는 룰을 신경 쓸 필요가 없다.
범위가 넓은 타입으로의 형변환도 명시적으로 형변환을 시켜주어야 한다.
모든 책임은 개발자에게 있다고 말하고 있는 듯하다.

var iAmFloat32 float32 = 12.34
var iAmFloat64 float54 = float64(iAmFloat32)

 

기본 타입에 널 할당

고는 기본 타입에 널을 할당할 수가 없다. 널을 할당하면 바로 컴파일 에러가 떠버린다.
널에 대해 걱정할 필요가 없어서 편하다.
대신 제로값(zero value) 라는 개념이 등장하는데 값이 할당되지 않을 경우 제로값으로 할당된다.
정수형 타입은 0, string 은 "" 로 할당되는데
자바처럼 필드변수일 때는 초기화되고 메서드에서는 초기화가 안되고 하는 룰 같은 거 없이 심플하게
다 제로값으로 초기화된다.
그리고 사용되지 않는 변수가 있으면 unused라고 컴파일 에러가 떠버린다.
리팩터링 할 때 혹시 지우지 않고 선언만 해둔 변수가 있을까 걱정할 필요가 없다.

package main

import "fmt"

func main() {
	// var iWantNilInt int
	// iWantNilInt = nil // 컴파일 에러 발생, 고에서 널은 nil

	var iAmInt int
	var iAmString string

	fmt.Println(iAmInt) // 0
	fmt.Println(iAmString) // ""
}

만약 데이터베이스 등에 널값을 넣어야 할 경우가 있다면 sql.NullString, sql.NullInt 같은 타입을 써야 한다.
sql.NullString 타입은 기본값 타입은 아니고 struct 인데 Valid 라는 bool 타입의 키의 값으로 null 을 판단한다.
Valid 가 false 이면 null 값으로 데이터베이스에 들어가고, 데이터베이스에서 널 값을 가져오면 Valid 가 false 로 할당된다.
개인적으로 Valid 라는 이름이 마음에 들지는 않는다. isNull 이 더 직관적으로 와닿기는 하는데 자바 개발자의 생각일 수도??
그리고 String 에 값을 넣고 Valid 를 false 로 할 수도 있는 부분도 마음에 들지는 않는다.
그렇게 코딩하는 개발자가 나쁜 거야!라고 하면 납득할 수밖에 없기는 하지만.

package main

import (
	"database/sql"
	"fmt"
)

func main() {
	// 이 값을 DB에 넣으면 널이 들어감
	var nullString sql.NullString = sql.NullString{String: "", Valid: false}

	fmt.Println(nullString) // {"" false}

	nullString = sql.NullString{String: "abc", Valid: true}

	// 널 처리
	if nullString.Valid {
		fmt.Println(nullString) // {abc true}
	} else {
		panic("Bomb!")
	}
}

 

멀티 리턴

고는 복수개의 값을 리턴할 수 있다.
자바의 경우 복수개의 값을 리턴하고 싶으면 dto 같은 데이터 클래스를 만들어 리턴하거나
맵을 이용 해야 하는데 고는 그냥 여러 값을 리턴해 버리면 된다.

package main

import "fmt"

func calc(a int, b int, op string) (int, string) {
	switch op {
	case "+":
		return a + b, op
	case "-":
		return a - b, op
	default:
		return 0, ""
	}
}

func main() {
	result, op := calc(4, 5, "+")

	fmt.Println(op)
	fmt.Println(result)
}

 

try - catch

고는 에러 처리를 위한 try - catch 문이 없다.
명시적으로 에러를 반환하고 그 값을 처리해야 한다.
자바에서 런타임 에러는 try - catch 를 하지 않아도 컴파일 에러가 뜨지 않아서
실수로 에러 핸들링을 깜빡할 수도 있으나 고에서는 이런 걱정은 없다.
언더스코어로 에러를 무시할 수도 있기는 하지만 개발자가 에러를 인지를 해야 가능해서
모든 것은 개발자이 책임이다.
고는 심플하고 엄격하게 개발자를 꾸짖는 언어인 것 같다.

package main

import (
	"errors"
	"fmt"
)

func calc(a int, b int, op string) (int, error) {
	switch op {
	case "+":
		return a + b, nil
	case "-":
		return a - b, nil
	case "*":
		return a * b, nil
	case "/":
		if b == 0 {
			return 0, errors.New("0으로 나누실려고요? 제정신이세요?")
		} else {
			return a / b, nil
		}
	default:
		return 0, errors.New("제대로 된 연산자가 아니에요! 저한테 왜 그러세요?")
	}
}

func main() {
	result, err := calc(4, 0, "/")

	// 에러 처리
	if err != nil {
		fmt.Println(err) // 0으로 나누실려고요? 제정신이세요?
	}

	fmt.Println(result) // 0, 마음에 들지 않음, 널 할당이 안되니 이런게 신경쓰이는 널의 노예 자바 개발자
}

 

접근 제한자

고는 접근제한자가 public, private 두 종류이다.
애당초 상속(확장)이 존재하지 않으니 protected 는 필요가 없다.
public 은 변수명, 함수명 시작을 대문자로 하면 되고
private 는 변수명, 함수명 시작을 소문자로 하면 된다.

// another.go 파일임
package another

// public, 접근 가능한 struct
type UpperCaseStruct struct {
	// public, 접근 가능한 필드
	UpperCaseField string
	// private, 접근 불가능한 필드
	lowerCaseField string
}

// private, 접근 불가능한 struct
type lowerCaseStruct struct {
	// public?? struct 가 private 이므로 접근 불가능, private??
	// func NewLowerCaseStruct 등의 메서드를 통해 인스턴스를 만든 후, 인스턴스를 통해 접근 가능
	UpperCaseField string
	// private, 접근 불가능한 필드
	lowerCaseField string
}



// main.go 파일임
package main

func main() {
	// 접근 가능한 struct 의 접근 가능한 필드
	upperCaseStruct := UpperCaseStruct{UpperCaseField: "I am upper"}    
}

 

포인터

고는 포인터를 제한적으로 사용할 수 있다. malloc, calloc, free 같은 포인터를 위한 메소드는 없고 포인터 연산도 없다.
자바에서는 메소드에 인자를 넘길 때 전부 pass by value 인데
고에서도 기본적으로는 pass by value 이나 포인터를 사용해 pass by refernce 로 만들 수 있다.

package main

import "fmt"

type human struct {
	name string
}

// * 연산자, 포인터 타입
func changeName(h *human) {
	h.name = "Micheal Jackson"
}

func main() {
	legend := human{}

	fmt.Println(legend.name) // ""

	// & 연산자, 참조주소
	changeName(&legend)

	fmt.Print(legend.name) // "Micheal Jackson"
}

 

상속, 인터페이스, 다형성

고는 상속(으로 쓰고 확장이라 읽는다)을 지원하지 않는다.
그래서 추상클래스가 존재하지 않아서 템플릿 메서드 패턴 같은 경우 구현하는 방법이 조금 다르다.
그리고 고의 인터페이스는 메서드만 정의할 수 있고 덕 타이핑을 지원하며,
덕 타이핑을 이용해서 인터페이스를 구현하고 다형성을 사용할 수 있다.
인터페이스를 구현하려면 인터페이스에 정의된 메서드를 구현한 struct 를 만들면 된다.

package main

import (
	"errors"
	"fmt"
)

// 인터페이스
type weapon interface {
	// 메서드만 선언 가능
	fight(string) error
}

// weapon 를 구현한 struct
type sword struct {
	what string
}

// sword 메서드, weapon 인터페이스의 fight 를 구현
func (s *sword) fight(who string) error {
	if who == "wizard" {
		return errors.New("마법사는 칼을 사용할 수 없습니다!")
	}
	fmt.Printf("%s가 %s을(를) 장비했습니다", who, s.what)

	return nil

}

// weapon 를 구현한 struct
type gun struct {
	what string
}

// gun 메서드, weapon 인터페이스의 fight 를 구현
func (g *gun) fight(who string) error {
	if who == "wizard" {
		return errors.New("마법사는 총을 사용할 수 없습니다!")
	}
	fmt.Printf("%s가 %s을(를) 장비했습니다", who, g.what)

	return nil
}

// weapon 구현체를 의존성 주입 시킬 struct
type useWeapon struct {
	w weapon
}

// useWeapon 생성자
func newUseWeapon(w weapon) *useWeapon {
	return &useWeapon{w: w}
}

func main() {
	// weapon 생성
	longSword := &sword{what: "long sword"}
	k2 := &gun{what: "k2"}

	// longSword 주입
	swordUse := newUseWeapon(longSword)
	swordUse.w.fight("king")

	fmt.Println()
	
	// k2 주입
	gunUse := newUseWeapon(k2)
	gunUse.w.fight("queen")
}

그 외

자바에서 목 객체를 만들거나 AOP 에서 사용하는 프락시 객체가 고에는 없기에 테스트에서 목 객체를 직접 코딩해줘야 한다.
명시적으로 눈에 보이는 것은 좋은 점이나 코딩 양이 늘어나는 게 단점이다. 물론 자동 생성해주는 라이브러리는 존재한다.
그리고 스프링에서 라이브러리를 받아 오려면 maven repository 를 사용해야 하는데(jcenter 는 문 닫는듯?)
고는 중앙화 된 서버에서 받아오는 것이 아니라 깃헙에서 받아올 수 있다.
깃헙에서 라이브러리를 받아 올 수 있다는 것은 로우레벨스러운 고를 보완해주는 최고의 조합이 아닐까 생각한다.
고는 심플해요, 맨땅에 헤딩하듯 만들어야 할 수도 있어요, 그러나 누군가 필요하면 라이브러리를 만들어서 깃헙에 올려놓겠죠,
누군가가 올려둔 라이브러리가 마음에 드시면 사용하세요, 대신 책임은 당신이 져야 해요
누구나 라이브러리를 만들어 깃헙에 올려둘 수 있다는 점이 오픈소스를 촉진한다는 점에서 좋다고 생각한다.
이게 고의 철학이 아닐까 생각한다.

728x90
반응형

댓글