U-그루비
Source: Dev.to
죄송하지만, 외부 URL에 직접 접근하여 해당 글의 내용을 가져올 수 없습니다. 번역이 필요한 텍스트를 여기 채팅에 복사해 주시면, 요청하신 대로 한국어로 번역해 드리겠습니다.
개요
저는 Jenkins 파이프라인에서 Groovy를 오랫동안 사용해 왔습니다.
이제 Java 분야로 더 많이 이동하고 있어서, 이 언어가 할 수 있는 일에 대한 짧은 요약을 할 좋은 시점입니다.
제 생각에 Groovy는 U‑language입니다: unknown(알려지지 않음), underrated(과소평가됨), unnoticed(눈에 띄지 않음). 많은 사람들이 들어본 적은 있지만, 실제로 얼마나 편리하고 범용적인지 아는 사람은 적습니다.
일반적으로 Groovy는 Java와 비슷합니다 – JVM 위에서 실행되고 순수 Java 코드를 이해하므로 Java 개발자가 즉시 사용할 수 있습니다.
Java와 달리 Groovy는 거의 모든 프로그래밍 패러다임에서 사용할 수 있어 코드베이스를 매우 유연하게 구조화할 수 있습니다. 아래는 이러한 유연성을 보여주는 몇 가지 간단한 예시입니다.
A. 명령형 프로그래밍
명령형 프로그래밍은 프로그램이 어떻게 단계별로 수행되는지를 설명합니다. 이는 컴퓨터 프로그램을 작성하는 가장 일반적인 방식입니다.
1. 스크립팅 언어
Groovy는 Bash와 마찬가지로 일반 스크립팅 언어로 사용할 수 있습니다:
println "this is just plain scripting"
a = 1
b = 2
println a + b
2. 동적 타입을 활용한 객체지향 프로그래밍
동적 타이핑을 사용하면서 객체지향 스타일로 작업할 수 있습니다:
class Car {
def regNumber = "123AB"
def numberOfStarts = 0
def weight = 2
def start() {
numberOfStarts++
println "running"
}
def stop() {
println "stopping"
}
def getRegNumber() {
return regNumber
}
// Example of a dynamic getter (not required for the demo)
def getNumberOfStarts() {
return numberOfStarts
}
}
def car = new Car()
car.start()
car.start()
assert car.getNumberOfStarts() == 2
println car.getRegNumber()
car = "this is car"
println car
정적 타이핑이 과도하게 느껴질 때는 코드를 단순화하고 정적 타이핑을 피할 수 있습니다.
동적 타이핑은 같은 변수(def car)에 서로 다른 타입의 값을 할당할 수 있음을 의미합니다. 메서드는 런타임 상황에 따라 서로 다른 타입을 반환할 수 있습니다.
3. 정적 타입을 활용한 객체지향 프로그래밍
Java와 더 비슷한 스타일을 선호한다면 정적 타입을 사용할 수 있습니다(세미콜론이 없다는 점에 주의하세요):
class Plane {
String regNumber = "123AB"
int weight = 20
int numberOfFlights
void fly() {
numberOfFlights++
println "flying"
}
void land() {
println "landing"
}
String getRegNumber() {
return regNumber
}
int getNumberOfFlights() {
return numberOfFlights
}
}
Plane plane = new Plane()
plane.fly()
plane.fly()
assert plane.getNumberOfFlights() == 2
println plane.getRegNumber()
B. 선언형 프로그래밍
선언형 프로그래밍은 무엇 결과가 되어야 하는지를 설명하고, 어떻게 달성할지는 설명하지 않습니다.
1. Groovy‑Based 선언형 구문
Groovy에서 사용자 정의 선언형 DSL(Domain‑Specific Languages)을 만들 수 있습니다:
println "declarative programming"
def surroundedWithDashes(closure) {
println "-------------"
closure.call()
println "-------------"
}
def show(closure) {
println closure.call()
}
def sumOfNumbers = { a, b -> a + b }
surroundedWithDashes {
show { sumOfNumbers(3, 6) }
show { sumOfNumbers(1, 2) }
show { sumOfNumbers(5, 10) }
}
DSL이 정의된 후 surroundedWithDashes { … }를 호출하면 원하는 레이아웃이 자동으로 생성됩니다.
2. 함수형 프로그래밍
Groovy는 순수 함수, 함수 체이닝, 메모이제이션, 커링과 같은 함수형 프로그래밍 개념을 지원합니다. 대부분의 작업은 collect, findAll, inject 세 가지 메서드만으로 표현할 수 있습니다.
println "functional programming"
def data = [1, 5, 8, 3, 5]
def result = data
.collect { it + 10 } // add 10 to each element
.findAll { it > 13 } // keep only values > 13
.toUnique() // remove duplicates
assert result == [15, 18]
이점은 체이닝뿐만 아니라 다음과 같은 함수형 기능에서도 얻을 수 있습니다:
- Memoization(메모이제이션) – 주어진 입력에 대한 함수 결과를 캐시합니다.
- Currying(커링) – 일반 함수의 일부 인자를 고정하여 새로운 함수를 생성합니다.
Source: …
C. 메타프로그래밍
메타프로그래밍은 아마도 Groovy의 특징이라고 할 수 있습니다. 컴파일 시점이나 런타임에 코드를 수정하거나 생성할 수 있게 해줍니다.
1. 컴파일‑시점 메타프로그래밍
AST(추상 구문 트리) 변환을 사용하여 컴파일 중에 코드를 생성할 수 있습니다:
println "compile-time metaprogramming"
@Log
class Car2 {
def regNumber
def numberOfStarts = 0
def weight = 2
@NullCheck
Car2(regNumber) {
this.regNumber = regNumber
}
def start() {
numberOfStarts++
log.info "log: running"
}
def stop() {
log.info "log: stopping"
}
String getRegNumber() {
return regNumber
}
}
try {
new Car2().start()
} catch (error) {
println "error was caught: ${error}"
}
new Car2("12345").start()
@Log는 로거를 주입하고, @NullCheck는 생성자에 널‑체크를 추가합니다. 소스 코드에 명시적인 널‑체크 코드를 작성할 필요가 없습니다.
2. 런타임 메타프로그래밍
런타임에 메서드와 프로퍼티를 가로채거나 주입, 심지어 합성까지 할 수 있습니다. 이 기능 덕분에 Groovy는 DSL을 만들거나, 테스트에서 객체를 모킹하거나, 기존 API를 즉석에서 적응시키는 데 매우 유연합니다.
예시
@Log
class Car3 {
def regNumber
def numberOfStarts = 0
def weight = 2
@NullCheck
Car3(regNumber) {
this.regNumber = regNumber
}
def start() {
numberOfStarts++
log.info "log: running"
}
def stop() {
log.info "log: stopping"
}
def getRegNumber() {
return regNumber
}
def methodMissing(String name, args) {
log.info "log: called missing method: ${name} with arguments: ${args}"
}
}
def car3 = new Car3("12345")
car3.someUnexistingMethod("with parameter")
car3.metaClass.additionalMethod = { param ->
log.info "log: called added method: additionalMethod with parameter: ${param}"
}
car3.additionalMethod("paramValue")
내장 메서드
methodMissing을 구현하면 클래스에 정의되지 않은 메서드가 호출될 때마다 이 메서드가 실행됩니다.
additionalMethod는 런타임에 클래스에 추가될 수 있으며 정상적으로 사용할 수 있습니다.
이 기능을 사용하는 이유
다음과 같은 경우에 매우 유용합니다:
-
테스트 – 특히 모킹이 필요할 때.
관련 기사 보기: Mocking with Groovy -
고급 작업 – 예를 들어 테스트 프레임워크를 작성할 때.
예시: GitHub에 있는 간소화된 Jenkins 테스트 프레임워크 – Jenkinson
Take‑away
- Groovy는 JVM 위에서 실행되며 순수 Java 코드를 이해합니다.
- 명령형, 선언형, 함수형, 메타프로그래밍 스타일을 지원합니다.
- 동적 또는 정적 타입(또는 두 가지를 혼합) 을 선택할 수 있습니다.
- DSL 구축 및 AST 변환 기능을 통해 도메인에 맞게 언어를 맞춤화할 수 있습니다.
Jenkins 파이프라인용 빠른 스크립트를 작성하든, 대규모 타입‑안전 애플리케이션을 구축하든, Groovy는 많은 Java 개발자들이 간과하는 다재다능한 도구 상자를 제공합니다. 즐거운 코딩 되세요!
요약
이것은 Groovy 코드를 작성할 수 있는 다양한 방법 중 간단한 예시들입니다. 저는 표면적인 부분만 다루었지만, 예시들이 언어에 대한 호기심과 흥미를 불러일으키길 바랍니다.
저 역시 Groovy를 계속 배우고 있지만, 여기서 보여준 모든 기능을 어느 정도 사용해 보았습니다. 포괄적인 문서는 다음을 참고하세요:
- Groovy Documentation:
아직 문서를 살펴보지 않으셨다면, 꼭 읽어보시길 권합니다—추가적인 언어 기능이 많이 있습니다.
제 생각에 이러한 유연성은 작업이나 프로젝트에 언어를 선택할 때 Groovy의 가장 큰 장점이며, 훌륭한 교육 도구이기도 합니다.
이 짧은 글이 도움이 되길 바랍니다.