STUDY/Go Lang

[3단계] Go 프로그래밍에 유용한 기법 익히기 (1)

Jexists 2023. 11. 5. 20:23

 

이 글은 골든래빗 《Tucker 의 Go 언어프로그래밍》의 3단계 써머리입니다.

목차

  1. 객체지향 설계 원칙 SOLID
  2. 테스트와 벤치마크
  3. Go 언어로 만드는 웹 서버
  4. RESTful API 서버 만들기

객체지향 설계 5가지 원칙 SOLID

설계

→ 프로그램 코드를 이루는 각 모듈 간 의존 관계 정의

나쁜 설계

→ 상호 결합도가 매우 높고 응집도가 낮다

  1. 경직성 (Rigidity): 모듈간의 결합도가 높아서 코드 변경하기 어려운 구조
  2. 부서지기 쉬움 (Fragility): 한 부분 수정 후 다른부분까지 망가지는 경우
  3. 부동성 (Immobility): 모듈간 결합도가 높아서 옮길 수 없는 경우(코드 재사용률 감소)

좋은 설계

→ 상호 결합도가 낮고 응집도가 높다

객체지향 설계 5가지 원칙 SOLID

  1. 단일 책임 원칙: Single Responsibility Principle (SRP)
  2. 개방-폐쇄 원칙: Open-Closed Principle (OCP)
  3. 리스코프 치환 원칙: Liskov Substitution Principle (LSP)
  4. 인터페이스 분리 원칙: Interface Segregation Principle (ISP)
  5. 의존 관계 역전 원칙: Dependency Inversion Principle (DIP)

1. 단일 책임 원칙: Single Responsibility Principle (SRP)

→ 모든 객체는 책임을 하나만 져야한다.

→ 코드 재사용성을 높여줍니다.

2. 개방-폐쇄 원칙: Open-Closed Principle (OCP)

→ 확장에는 열려 있고 변경에는 닫혀 있다.

→ 상호 결합도를 줄여 새 기능을 추가할 때 기존 구현을 변경하지 않아도 됨

3. 리스코프 치환 원칙: Liskov Substitution Principle (LSP)

→ q(x)를 타입 T의 객체 x에 대해 증명할 수 있는 속성인 경우, S가 T의 하위 타입이면 q(y)는 타입 S의 객체 y에 대해 증명 가능

→ 상위 타입을 인수로 받는 함수는 하위 타입 인수에도 동작해야 한다.

→ 예상치 못한 작동을 예방

4. 인터페이스 분리 원칙: Interface Segregation Principle (ISP)

→ 클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야한다.

→ 인터페이스를 분리하면 불필요한 메서드들과 의존 관계가 끊어져 더 가볍게 인터페이스를 이용할 수 있다.

5. 의존 관계 역전 원칙: Dependency Inversion Principle (DIP)

→ 구체화된 객체는 추상화된 객체와 의존 관계를 가져야 한다.

→ 상위 계층이 하위 계층에 의존하는 전통적인 의존 관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다.

→ 상위 모듈은 하위 모듈에 의존해서는 안된다. (둘다 추상 모듈에 의존)

→ 추상모듈은 구체화된 모듈에 의존해서는 안된다. (구체화된 모듈은 추상모듈에 의존)

→ 구체화된 모듈이 아닌 추상 모듈에 의존함으로써 확장성이 증가

→ 상호 결합도가 낮아져서 다른 프로그램으로 이식성이 증가

테스트와 벤치마크

테스트 코드

→ 파일명이 _test.go로 끝나야함

→ import "testing" 패키지 임포트

→ func TestXxxx(t *testing.T)형태

→ $go test -run 테스트명

→ 테스트하고 테스트 실패를 알려주는 외부 패키지: stretchr/tesetify

블랙박스 테스트

→ 제품 내부를 오픈하지 않은 상태에서 진행되는 테스트

→ 사용자 입장에서 테스트

→ 사용성 테스트: usability test

→ 프로그램을 실행한 상태로 실행 동작을 검사하는 방식

→ 전문 테스터, QV, QA 직군에서 주로 담당

→ 내부 코드를 검증하지 않고 제품 전체의 사용성을 검사하는 방식 (코드 내부에 잠재되어 있는 버그 찾는 데 어려움)

화이트박스 테스트

→ 프로그램 내부 코드를 직접 검증하는 방식

→ 유닛 테스트(단위 테스트): unit test

→ 직접 테스트 코드를 작성해서 내부 테스트를 검사하는 방식

→ 코드를 직접 검사할 수 있지만 사용자 입장에서 전체 서비스를 검사하는데 어려움

테스트 주도 개발

  • Test Driven Development (TDD)
  • 테스트 코드 작성 시기를 코드 작성 이전으로 옮긴 방식
  • 테스트 코드 작성 → 테스트 실패 → 코드 작성 → 테스트 성공 → 개선 → 완성

벤치마크

  • 코드 성능 검사
  • 파일명이 _test.go로 끝나야함
  • import "testing" 패키지 임포트
  • func BenchmarkXxxx(b *testing.B) 형태
  • $go test -bench .

Go 언어로 만드는 웹 서버

웹서버 시작

package main

import (
	"fmt"
	"net/http"
)

func main() {
	// 웹 핸들러 등록
	// http.HandleFunc("경로",func(w http.ResponseWriter, r *http.Request) {
	// 	fmt.Fprint(w, "Hello World")
	// })
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello World")
	})

	// 웹 서버 시작
  // func ListenAndServe(addr string, handler Handler) error
	http.ListenAndServe(":3000", nil)
	// <http://127.0.0.1:3000/>
	// <http://localhost:3000/>
}

쿼리값 받기

package main

import (
	"fmt"
	"net/http"
	"strconv"
)

func barHandler(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()    // 쿼리 인수
	name := values.Get("name") // 특정 키값 있는지 확인
	if name == "" {
		name = "World"
	}
	id, err := strconv.Atoi(values.Get("id"))
	if err != nil {
		// 에러처리
	}
	fmt.Fprintf(w, "Hello %s! id:%d", name, id)
}
func main() {
	http.HandleFunc("/bar", barHandler)

	// 웹 서버 시작
	http.ListenAndServe(":3000", nil)
	// <http://localhost:3000/bar> = Hello World! id:0
	// <http://localhost:3000/bar?name=joy&id=7> = Hello joy! id:7
	// <http://localhost:3000/> = 404 page not found

}

ServeMux

→ multiplexer: 멀티플렉서의 약자 (Router: 라우터)—————

→ 여러입력 중 하나를 선택해서 반환하는 디지털 장치

package main

import (
	"fmt"
	"net/http"
)

func main() {
	// serveMux 인스턴스 생성
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello World")
	})

	mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello Bar")
	})

	// mux 인스턴스 사용
	http.ListenAndServe(":3000", mux)
}