Golang

jingyu·2022년 6월 28일
0

Golang

목록 보기
1/3

Go 언어의 특징과 사용하면서 조사했던 문법적 요소들을 작성하였다.

특징

1) 컴파일 언어/빠른 컴파일 속도
: c와 비슷하지만 c언어의 복잡도를 줄이고 컴파일 속도를 향상시켰다.
: 컴파일 속도가 빨라 인터프리터(작성하고 바로 실행) 언어처럼 쓸 수 있다.
: 문법이 간결하고 접근하기 쉽다.
: 사용하지 않는 변수나 패키지가 있을 경우 컴파일 오류를 발생
2) 다양한 패키지 제공
: 여러 벤더에서 다양한 패키지를 제공
: http 패키지는 물론 통신에 필요한 암호화 모듈까지 기본 내장되어 웹 서버 구축을 쉽게할 수 있다.
3) 동시성
: go루틴은 경량 쓰레드이며 채널을 통해 go루틴 간에 메세지를 주고 받을 수 있는 메커니즘을 제공한다.
: go루틴과 채널을 활용해 머티 코어 환경에서 병렬처리를 쉽게 구현할 수 있다.
4) 구글이 개발해서 신뢰성이 높다.
:유투브, k8s, docker등의 개발 언어

단점

1) 포인터 연산을 없애서 커널, 드라이버, 임베디드 소프트웨어 개발에는 적합하지 않음
2) 제네릭 타입, 클래스 타입, 예외 처리, Public, Private등이 없다.
3) Go 사용 레퍼런스가 국내에 많이 없다.

패키지 생성

1) go mod init 명령어

:패키지 초기화 명령어, go.mod 파일 생성됨
:go.mod는 의존성 패키지 정보를 생성

2) go mod vendor

:vendor 디렉토리를 만들고 go.mod에 명시된 의존성 패키지들이 다운로드 됨
:modules.txt 파일이 생성
:modules.txt는 상세한 하위 패키지와 디렉토리가 기술되어 저장

문법

1) defer 키워드

Go 언어의 defer 키워드는 특정 문장 혹은 함수를 나중에 (defer를 호출하는 함수가 리턴하기 직전에) 실행하게 한다.

일반적으로 defer는 C#, Java 같은 언어에서의 finally 블럭처럼 마지막에 Clean-up 작업을 위해 사용된다.

아래 예제는 파일을 Open 한 후 바로 파일을 Close하는 작업을 defer로 쓰고 있다. 이는 차후 문장에서 어떤 에러가 발생하더라도 항상 파일을 Close할 수 있도록 한다.

package main 
import "os"
 
func main() {
    f, err := os.Open("1.txt")
    if err != nil {
        panic(err)
    }
 
    // main 마지막에 파일 close 실행
    defer f.Close()
 
    // 파일 읽기
    bytes := make([]byte, 1024)
    f.Read(bytes)
    println(len(bytes))
}

2) golang error 처리

"log" 패키지에서 제공하는 에러 출력 함수입니다.
각 함수는 에러 값을 출력하고 처리하는 방식이 다릅니다.

당연히 이 함수들을 사용하기 위해서 "log"를 import 해야합니다.

func Fatal(v ...interface{}) : 에러 로그 출력 및 프로그램 종료
func Panic(v ...interface{}) : 시간, 에러 메시지 출력 및 패닉 발생, defer 구문이 없을 시 런타임 패닉을 발생시키고 콜스택 출력
func Print(v ...interface{}) : 시간, 에러 메시지 출력 하지만 프로그램 종료하지 않음

log.Print(err): 에러 메시지만 출력합니다. 따라서 중요하지 않은 문제에만 적용하는 것이 좋습니다.

log.Panic(err) : panic이 발생하기 전에 defer 구문을 실행합니다. 마지막으로 defer 구문의 익명 함수 안에 있는 log.Fatal(err)이 실행되고 프로그램이 완전히 종료됩니다.

따라서 실행된 순서로 치명도가 높은 경우에 적용하면 되는 에러 출력 함수입니다.
치명적인 예외 상황일수록 뒤에 사용된 에러 출력 함수를 사용해야합니다.

log.Panic(err)과 log.Fatal(err)의 차이로는 Panic은 런타임 에러를 발생시키고 프로그램을 종료하고 Fatal은 프로그램을 정상적으로 완전히 종료합니다.
그리고 log.Panic()과 Panic() 함수는 같은 역할을 합니다.

3) Receiver 메서드

리시버 인자를 가진 함수를 말함
기능적으로 보면 일반함수랑 차이가 없고 문법은 func 키워드와 메서드 이름 사이에 리시버 인자를 추가할 수 있다.

func (receiver_name Type) methodName(parameter_list) (return_type) 

예제

type Car struct {
	brand   string
	color   string
	mileage int
	speed   int
}

func (c Car) Color() string {
	return c.color
}

//Car 타입의 값을 메서드 형식으로 반환하려면 메서드 이름 앞에 리시버 인자로 Car 타입을 선언하면 된다. 
//Color() 메서드에서는 c.color 값을 반환한다.


func Example_Method_Value_Receiver() {
	hyundaiCar := Car{"현대", "빨강", 10000, 0}
	//fmt.Println("hyundaiCar", hyundaiCar)

	fmt.Println(hyundaiCar.Color())

	//Output:
	//빨강
}

4) Duck Typing

"그게 오리인지 검사하지 말고, 당신이 오리의 무슨 행동이 필요한지에 따라서 오리처럼 우는지, 오리처럼 걷는지 기타 등등... 적절한 행동을 오리처럼 하는지 검사 하세요"

덕타이핑은 객체를 인터페이스에 대입할 때 (studentInterface = student) 해당 객체가 인터페이스가 필요로 하는 메서드를 갖고있는지 체크하고 그렇지 않다면 컴파일 에러를 발생시킨다.

이러한 방법이 타입을 선언할 때 인터페이스 구현여부를 밝히지 않고 인터페이스가 사용될 때 결정하기 때문에 사용자 중심적인 프로그래밍이 가능해진다.

즉, 일단 객체를 만들어서 사용을 하다가 인터페이스가 필요하다 싶으면 그때 인터페이스를 정의해서 사용하면 된다.

GO 언어적 특성
Go는 컴파일 기반의 정적 Type 언어이다(type을 미리 선언해서 사용)
하지만 동적 언어의 특성 또한 수용해서 동적 언어 스타일의 코딩이 가능하다.
즉 컴파일러의 보장을 받으면서 동적 언어의 장점을 사용할 수 있다.
이것이 가능한게 덕타이핑 방식으로 작동하는 Go의 인터페이스이다.

Duck Typing 예제

type Duck interface {
    Quack() string // 쾍쾍 거리는 동작
    Walk() string  // 걷는 동작
}

Duck 인터페이스를 구현한 구조체
구조체에 Duck 인터페이스라는 어떠한 키워드도 없고 구현에 대한 강제성도 없다.
하지만 구조체를 사용하여 변수를 생성하고 해당 변수를 Duck 인터페이스에 할당할 수 있다.

type Swan struct {
	name string
}

type Lion struct {
	name string
}

func (s Swan) Quack() string {
	return "새: " + s.name + " 울름소리"
}
func (s Swan) Walk() string {
	return "새: " + s.name + " 걷는 모습"
}

func (l Lion) Quack() string {
	return "맹수: " + l.name + " 울름소리"
}
func (l Lion) Walk() string {
	return "맹수: " + l.name + " 걷는 모습"
}

Duck 인터페이스를 구현한 모든 구조체를 Duck 타입의 파라미터로 받을 수 있다.

만약 Duck 인터페이스에 선언된 Quack(), Walk() 함수들이 구조체에 구현되어 있지 않으면 Duck 인터페이스의 구현체가 아니므로 파라미터로 넘길 수 없다.

func Scare(duck Duck) {
	// Duck의 동작을 구현한 어떤 객체라도 argument로 전달 될 수 있다.
	log.Println(duck.Quack())
	log.Println(duck.Walk())
}

실행

Swan, Lion 구조체를 보면 Duck 인터페이스를 구현한다는 명시적 키워드가 보이지 않는다.
그저 Duck 인터페이스의 동작을 구현함으로써 이들을 Duck 인터페이스 타입으로 사용될 수 있다.

func main() {
	var s Swan
	s.name = "백조"
	Scare(s)

	var l Lion
	l.name = "사자"
	Scare(l)
}

결과
-------------------------------------
INFO[0000] 새: 백조 울름소리                                   
INFO[0000] 새: 백조 걷는 모습                                  
INFO[0000] 맹수: 사자 울름소리                                  
INFO[0000] 맹수: 사자 걷는 모습   
-------------------------------------

func main() {
	var duck Duck
	duck = s  // interface 직접 대입 가능
	log.Println(duck.Quack())
	duck = l
	log.Println(duck.Quack())
}

결과
-------------------------------------
INFO[0000] 새: 백조 울름소리                                   
INFO[0000] 맹수: 사자 울름소리    
-------------------------------------

에러

func main() {
	var human Duck = Human{} // 컴파일 에러 발생, Duck 인터페이스를 구현하지 않았음
    Scare(human)
}

덕 타이핑 개념은 프로그래머의 실수에 의한 오류가 항상 존재함으로 언어측면에서 강제 해야한다는 의견도 있고

누구는 그런 제약이 존재함으로써 복잡도가 증가하고 유지보수 비용이 많이 든다는 의견도 있고

누구는 그런 자유도 때문에 생산성이 향상되고 확장이 용이하다는 의견도 있다.

→ 자바와는 다르게 Go에서는 인터페이스 구현에 대한 강제성이 없고 해당 인터페이스의 구현체라는 키워드도 볼 수 없다.

인터페이스의 구현은 구조체를 사용해서 구현해야 한다.

위 예제처럼 구조체 변수를 인터페이스(Duck)타입으로 받을때, 즉 구조체를 인터페이스(Duck)타입으로 대입시킬때 구조체는 인터페이스에 정의된 함수들의 구현이 반드시 필요하다.

여기서 구현에 대한 강제성이 들어간다.

그럼 왜 인터페이스를 사용하냐?

각기 다른 기능을 하는 서비스들을 공통적인 기능으로 묶을때 사용할 수 있다.

Rclone에서는 설정파일 정보를 생성할때 파일 스토리지, 환경변수 스토리지, 다른 스토리지,... 스토리지 종류는 많은데 이를 공통적인 기능들로 정의해서 사용한다.

이럴경우 Rclone은 어떤 스토리지를 사용하는지 상관없이 인터페이스 객체(Duck 인터페이스처럼) 받아서 사용하기만 하면 된다.

참고: https://www.popit.kr/golang%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%82%98%EB%B3%B4%EB%8A%94-duck-typing/

https://blog.advenoh.pe.kr/go/Go%EC%97%90%EC%84%9C%EC%9D%98-%EB%A9%94%EC%84%9C%EB%93%9C-Method-in-Go/

profile
내일을 향해 쏴라!

0개의 댓글