Avatar
πŸ˜‰

Organizations

  • μ•„λž˜μ— Javaμ—μ„œ PIT (Pitest) Mutation Testing을 μ‚¬μš©ν•˜λŠ” 방법을 λ‹¨κ³„λ³„λ‘œ 정리해 λ“œλ¦½λ‹ˆλ‹€. PITλŠ” μžλ°” μ½”λ“œμ˜ ν…ŒμŠ€νŠΈ 컀버리지λ₯Ό μ‹€μ œλ‘œ κ²€μ¦ν•˜λŠ” κ°•λ ₯ν•œ λ„κ΅¬λ‘œ, 잘λͺ»λœ(λ³€μ΄λœ) μ½”λ“œλ₯Ό ν…ŒμŠ€νŠΈκ°€ μ–Όλ§ˆλ‚˜ 잘 μž‘μ•„λ‚΄λŠ”μ§€λ₯Ό μΈ‘μ •ν•©λ‹ˆλ‹€.


    πŸ“Œ 1. PIT(Pitest) μ†Œκ°œ

    • Mutation Testingμ΄λž€?

      • κΈ°μ‘΄ μ½”λ“œλ₯Ό μΌλΆ€λŸ¬ β€œλ³€μ΄(mutant)” μ‹œμΌœμ„œ ν…ŒμŠ€νŠΈλ₯Ό λŒλ €λ³΄λŠ” κΈ°λ²•μž…λ‹ˆλ‹€.
      • ν…ŒμŠ€νŠΈκ°€ 이 변이λ₯Ό κ²€μΆœν•˜μ§€ λͺ»ν•˜λ©΄, ν…ŒμŠ€νŠΈμ— ν—ˆμ μ΄ μžˆλ‹€λŠ” λœ»μž…λ‹ˆλ‹€.
      • λ‹¨μˆœν•œ 라인 컀버리지보닀 더 μ‹ λ’°μ„± μžˆλŠ” ν…ŒμŠ€νŠΈ ν’ˆμ§ˆ μ§€ν‘œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
    • PIT의 νŠΉμ§•

      • JUnit 기반 ν…ŒμŠ€νŠΈμ™€ 잘 ν†΅ν•©λ©λ‹ˆλ‹€.
      • Maven, Gradle, Antμ—μ„œ μ‰½κ²Œ μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€.
      • HTML 및 XML 리포트λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

    βš™οΈ 2. Maven ν”„λ‘œμ νŠΈμ— PIT 적용

    2.1 Maven ν”ŒλŸ¬κ·ΈμΈ μΆ”κ°€

    pom.xml에 λ‹€μŒ ν”ŒλŸ¬κ·ΈμΈμ„ μ„€μ •ν•©λ‹ˆλ‹€:

    Created Thu, 03 Jul 2025 10:44:24 +0900
  • Spring Boot 3μ—μ„œ SSH 터널을 ν†΅ν•œ PostgreSQL 연결을 μœ„ν•œ μ„€μ • 방법을 μ•Œλ €λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.

    1. application.yml μ„€μ •

    spring:
      datasource:
        url: jdbc:postgresql://localhost:5433/your_database_name
        username: your_db_username
        password: your_db_password
        driver-class-name: org.postgresql.Driver
      
      jpa:
        hibernate:
          ddl-auto: validate
        show-sql: true
        properties:
          hibernate:
            dialect: org.hibernate.dialect.PostgreSQLDialect
            format_sql: true
    
    # SSH 터널 μ„€μ • (μ»€μŠ€ν…€ ν”„λ‘œνΌν‹°)
    app:
        ssh:
          tunnel:
            host: your-ssh-server.com
            port: 22
            username: ssh_username
            private-key-path: ~/.ssh/id_rsa
            local-port: 5433
            remote-host: localhost  # SSH μ„œλ²„μ—μ„œ λ³Έ DB 호슀트
            remote-port: 5432       # μ‹€μ œ PostgreSQL 포트
    

    2. SSH 터널 μžλ™ 연결을 μœ„ν•œ Configuration 클래슀## 3. ν•„μš”ν•œ μ˜μ‘΄μ„± μΆ”κ°€ (build.gradle)

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.postgresql:postgresql'
        implementation 'com.github.mwiede:jsch:0.2.17'
        
        // 기타 ν•„μš”ν•œ μ˜μ‘΄μ„±λ“€...
    }
    

    3. ssh key λ§Œλ“€κΈ° μ˜ˆμ‹œ

    ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/id_rsa -C "ceo@pink-spider.io"
    
    • DBκ°€ μ„€μΉ˜λœ μ„œλ²„μ— ~/.ssh 등에 authorized_keys (κΆŒν•œ 644) νŒŒμΌμ— id_rsa.pub λ‚΄μš© μΆ”κ°€ν•΄μ•Ό λ©λ‹ˆλ‹€.
    • rsaκ°€ μ•„λ‹Œ OpenSSh둜 keygen ν–ˆμ„ 경우 com.github.mwiede:jsch κ°€ μ•„λ‹Œ λ‹€λ₯Έ 라이브러리λ₯Ό 써야 λ©λ‹ˆλ‹€.

    SshTunnelConfig

    
    @Configuration
    @Slf4j
    @Profile({"local", "dev", "stage", "prod"})
    public class SshTunnelConfig {
    
        @Value("${app.ssh.tunnel.host}")
        private String sshHost;
    
        @Value("${app.ssh.tunnel.port}")
        private int sshPort;
    
        @Value("${app.ssh.tunnel.username}")
        private String sshUsername;
    
        @Value("${app.ssh.tunnel.private-key-path}")
        private String privateKeyPath;
    
        @Value("${app.ssh.tunnel.local-port}")
        private int localPort;
    
        @Value("${app.ssh.tunnel.remote-host}")
        private String remoteHost;
    
        @Value("${app.ssh.tunnel.remote-port}")
        private int remotePort;
    
        @Value("${app.ssh.tunnel.passphrase:}")
        private String passphrase;
    
        private Session session;
    
        @PostConstruct
        public void createSshTunnel() {
            try {
                log.info("JSch SSH 터널 μ—°κ²° μ‹œμž‘...");
    
                // 둜컬 ν¬νŠΈκ°€ μ‚¬μš© κ°€λŠ₯ν•œμ§€ 확인
                if (!isPortAvailable(localPort)) {
                    throw new RuntimeException("둜컬 포트 " + localPort + "κ°€ 이미 μ‚¬μš© μ€‘μž…λ‹ˆλ‹€.");
                }
    
                JSch jsch = new JSch();
    
                // κ°œμΈν‚€ 파일 경둜 처리
                String keyPath = privateKeyPath;
                if (keyPath.startsWith("~/")) {
                    keyPath = System.getProperty("user.home") + keyPath.substring(1);
                }
    
                // 파일 쑴재 및 읽기 κΆŒν•œ 확인
                File keyFile = new File(keyPath);
                if (!keyFile.exists()) {
                    throw new RuntimeException("SSH κ°œμΈν‚€ νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: " + keyPath);
                }
    
                if (!keyFile.canRead()) {
                    throw new RuntimeException("SSH κ°œμΈν‚€ νŒŒμΌμ„ 읽을 수 μ—†μŠ΅λ‹ˆλ‹€. 파일 κΆŒν•œμ„ ν™•μΈν•˜μ„Έμš”: " + keyPath);
                }
    
                log.info("SSH κ°œμΈν‚€ 파일: {}", keyPath);
    
                // ν‚€ ν˜•μ‹ 확인 (RSA ν˜•μ‹μΈμ§€ 체크)
                try (java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(keyFile))) {
                    String firstLine = reader.readLine();
                    log.info("ν‚€ 파일 ν˜•μ‹: {}", firstLine);
    
                    if (firstLine != null && !firstLine.contains("BEGIN RSA PRIVATE KEY")) {
                        log.warn("RSA ν˜•μ‹μ΄ μ•„λ‹Œ ν‚€κ°€ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€: {}", firstLine);
                        log.warn("RSA ν˜•μ‹ ν‚€λ₯Ό μƒμ„±ν•˜λ €λ©΄: ssh-keygen -t rsa -b 4096 -m PEM -f {}", keyPath);
                    }
                }
    
                // κ°œμΈν‚€ μΆ”κ°€
                if (passphrase != null && !passphrase.trim().isEmpty()) {
                    jsch.addIdentity(keyPath, passphrase);
                    log.info("νŒ¨μŠ€ν”„λ ˆμ΄μ¦ˆμ™€ ν•¨κ»˜ κ°œμΈν‚€ λ‘œλ“œ μ™„λ£Œ");
                } else {
                    jsch.addIdentity(keyPath);
                    log.info("κ°œμΈν‚€ λ‘œλ“œ μ™„λ£Œ");
                }
    
                // SSH μ„Έμ…˜ 생성
                session = jsch.getSession(sshUsername, sshHost, sshPort);
                session.setConfig("StrictHostKeyChecking", "no");
                session.setConfig("PreferredAuthentications", "publickey");
                session.setTimeout(30000);
    
                log.info("SSH μ„œλ²„ μ—°κ²° μ‹œλ„: {}@{}:{}", sshUsername, sshHost, sshPort);
                session.connect();
                log.info("SSH μ—°κ²° 성곡");
    
                // 포트 ν¬μ›Œλ”© μ„€μ •
                session.setPortForwardingL(localPort, remoteHost, remotePort);
    
                // ν¬νŠΈκ°€ 열릴 λ•ŒκΉŒμ§€ λŒ€κΈ°
                waitForPortToOpen(localPort, 10000);
    
                log.info("SSH 터널이 μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€: localhost:{} -> {}:{}:{}",
                    localPort, sshHost, remoteHost, remotePort);
    
            } catch (JSchException e) {
                log.error("SSH 터널 생성 μ‹€νŒ¨: {}", e.getMessage(), e);
    
                // ꡬ체적인 μ—λŸ¬ λ©”μ‹œμ§€ 제곡
                if (e.getMessage().contains("invalid privatekey")) {
                    log.error("κ°œμΈν‚€ ν˜•μ‹μ΄ 잘λͺ»λ˜μ—ˆμŠ΅λ‹ˆλ‹€. RSA ν˜•μ‹ ν‚€λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.");
                    log.error("μƒˆ RSA ν‚€ 생성: ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/level_up_rsa");
                } else if (e.getMessage().contains("Auth fail")) {
                    log.error("인증에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. κ³΅κ°œν‚€κ°€ μ„œλ²„μ— λ“±λ‘λ˜μ–΄ μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.");
                    log.error("κ³΅κ°œν‚€ 등둝: ssh-copy-id -i {} {}@{}", privateKeyPath + ".pub", sshUsername, sshHost);
                } else if (e.getMessage().contains("Connection refused")) {
                    log.error("μ„œλ²„ 연결이 κ±°λΆ€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. ν˜ΈμŠ€νŠΈμ™€ 포트λ₯Ό ν™•μΈν•˜μ„Έμš”.");
                }
    
                cleanupResources();
                throw new RuntimeException("SSH 터널 연결에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: " + e.getMessage(), e);
            } catch (Exception e) {
                log.error("μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜: {}", e.getMessage(), e);
                cleanupResources();
                throw new RuntimeException("SSH 터널 μ„€μ • 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: " + e.getMessage(), e);
            }
        }
    
        @PreDestroy
        @EventListener(ContextClosedEvent.class)
        public void closeSshTunnel() {
            log.info("SSH 터널 μ’…λ£Œ μ‹œμž‘...");
            cleanupResources();
            log.info("SSH 터널 μ’…λ£Œ μ™„λ£Œ");
        }
    
        private void cleanupResources() {
            if (session != null && session.isConnected()) {
                try {
                    session.disconnect();
                    log.info("SSH μ„Έμ…˜μ΄ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
                } catch (Exception e) {
                    log.warn("SSH μ„Έμ…˜ μ’…λ£Œ 쀑 였λ₯˜: {}", e.getMessage());
                }
            }
        }
    
        private boolean isPortAvailable(int port) {
            try (ServerSocket serverSocket = new ServerSocket(port)) {
                return true;
            } catch (Exception e) {
                return false;
            }
        }
    
        private void waitForPortToOpen(int port, long timeoutMillis) throws InterruptedException {
            long startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < timeoutMillis) {
                try (java.net.Socket socket = new java.net.Socket()) {
                    socket.connect(new java.net.InetSocketAddress("localhost", port), 1000);
                    log.info("포트 {}κ°€ μ„±κ³΅μ μœΌλ‘œ μ—΄λ ΈμŠ΅λ‹ˆλ‹€.", port);
                    return;
                } catch (Exception e) {
                    // ν¬νŠΈκ°€ 아직 열리지 μ•ŠμŒ, μž¬μ‹œλ„
                    Thread.sleep(500);
                }
            }
            log.warn("포트 {}κ°€ {}ms 내에 열리지 μ•Šμ•˜μŠ΅λ‹ˆλ‹€.", port, timeoutMillis);
        }
    }
    
    Created Thu, 03 Jul 2025 08:44:24 +0900
  • **Property-Based Testing(PBT)**은 μž…λ ₯κ³Ό κ²°κ³Ό κ°„μ˜ **일반적인 속성(Property)**을 μ •μ˜ν•˜κ³ , ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬κ°€ κ·Έ 속성을 μΆ©μ‘±ν•˜λŠ”μ§€λ₯Ό λ¬΄μž‘μœ„λ‘œ μƒμ„±λœ λ‹€μ–‘ν•œ μž…λ ₯값에 λŒ€ν•΄ ν™•μΈν•˜λŠ” λ°©μ‹μ˜ ν…ŒμŠ€νŠΈμž…λ‹ˆλ‹€. λ‹¨μœ„ ν…ŒμŠ€νŠΈμ²˜λŸΌ νŠΉμ • μž…λ ₯κ°’λ§Œ ν™•μΈν•˜λŠ” 것이 μ•„λ‹ˆλΌ, 수천 개의 μž…λ ₯ 쑰합을 μžλ™μœΌλ‘œ 검사할 수 μžˆλ‹€λŠ” μ μ—μ„œ 맀우 κ°•λ ₯ν•©λ‹ˆλ‹€.


    πŸ” Property-Based Testingμ΄λž€?

    βœ… 핡심 κ°œλ…

    • 속성(Property): ν…ŒμŠ€νŠΈ λŒ€μƒ ν•¨μˆ˜λ‚˜ 둜직이 항상 λ§Œμ‘±ν•΄μ•Ό ν•˜λŠ” 일반적인 법칙 λ˜λŠ” κ·œμΉ™.
    • μž…λ ₯κ°’ μžλ™ 생성: PBT ν”„λ ˆμž„μ›Œν¬κ°€ λ‹€μ–‘ν•œ μž…λ ₯값을 λ¬΄μž‘μœ„λ‘œ 생성.
    • Fail case μΆ•μ†Œ(Shrinking): μ‹€νŒ¨ν•œ μž…λ ₯값이 λ°œμƒν–ˆμ„ λ•Œ, μ‹€νŒ¨λ₯Ό μž¬ν˜„ν•  수 μžˆλŠ” μ΅œμ†Œ μž…λ ₯κ°’μœΌλ‘œ μ€„μ—¬μ„œ 디버깅을 μ‰½κ²Œ 함.

    πŸ†š λ‹¨μœ„ ν…ŒμŠ€νŠΈμ™€μ˜ 비ꡐ

    ν•­λͺ© λ‹¨μœ„ ν…ŒμŠ€νŠΈ (Example-based) 속성 기반 ν…ŒμŠ€νŠΈ (Property-based)
    μž…λ ₯κ°’ κ³ μ •λœ 사둀 μˆ˜λ™ μ§€μ • μžλ™ μƒμ„±λœ λ‹€μ–‘ν•œ κ°’
    ν…ŒμŠ€νŠΈ λ²”μœ„ μ œν•œμ  맀우 κ΄‘λ²”μœ„
    μœ μ§€λ³΄μˆ˜ 둜직 λ³€κ²½ μ‹œ ν…ŒμŠ€νŠΈ μΆ”κ°€ ν•„μš” Property만 잘 μ •μ˜ν•˜λ©΄ μžλ™ 적용
    μ‹€νŒ¨ μ‹œ 디버깅 λͺ…ν™•ν•œ μž…λ ₯κ°’ μΆ•μ†Œ κ³Όμ • ν•„μš”

    πŸ§ͺ Spring Bootμ—μ„œ Property-Based Test μ‚¬μš©ν•˜λŠ” 방법

    Spring Boot μžμ²΄λŠ” JUnit을 기반으둜 ν•œ λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ 기본이며, PBTλŠ” μ™ΈλΆ€ 라이브러리λ₯Ό μ‚¬μš©ν•΄μ„œ ν†΅ν•©ν•˜λŠ” 것이 μΌλ°˜μ μž…λ‹ˆλ‹€. κ°€μž₯ 널리 μ“°μ΄λŠ” Java 기반 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

    Created Fri, 27 Jun 2025 10:18:03 +0900
  • Ubuntuμ—μ„œ PostgreSQL을 μ„€μΉ˜ν•˜κ³  μ™ΈλΆ€ 접속을 ν—ˆμš©ν•˜λ €λ©΄ λ‹€μŒκ³Ό 같은 λ‹¨κ³„λ‘œ μ§„ν–‰ν•˜λ©΄ λ©λ‹ˆλ‹€.


    1. PostgreSQL μ„€μΉ˜

    sudo apt update
    sudo apt install postgresql postgresql-contrib
    

    μ„€μΉ˜ ν›„ PostgreSQL μ„œλΉ„μŠ€κ°€ μžλ™ μ‹œμž‘λ©λ‹ˆλ‹€. μƒνƒœ 확인:

    sudo systemctl status postgresql
    

    2. PostgreSQL μœ μ € 및 DB μ„€μ •

    PostgreSQL μ„€μΉ˜ μ‹œ 기본적으둜 postgresλΌλŠ” μœ μ €κ°€ μƒμ„±λ©λ‹ˆλ‹€.

    sudo -i -u postgres
    psql
    

    μ›ν•˜λŠ” μœ μ €μ™€ DBλ₯Ό λ§Œλ“€κ³  λΉ„λ°€λ²ˆν˜Έ μ„€μ •:

    CREATE USER myuser WITH PASSWORD 'mypassword';
    CREATE DATABASE mydb OWNER myuser;
    

    κΆŒν•œ λΆ€μ—¬:

    Created Mon, 23 Jun 2025 10:14:00 +0900
  • 컀링(Currying)은 μ—¬λŸ¬ 개의 인자λ₯Ό λ°›λŠ” ν•¨μˆ˜λ₯Ό ν•˜λ‚˜μ˜ 인자만 λ°›λŠ” ν•¨μˆ˜λ“€μ˜ μ—°μ†μœΌλ‘œ λ³€ν™˜ν•˜λŠ” κΈ°λ²•μž…λ‹ˆλ‹€. μˆ˜ν•™μž ν•˜μŠ€μΌˆ 컀리(Haskell Curry)의 μ΄λ¦„μ—μ„œ λ”°μ˜¨ μš©μ–΄μž…λ‹ˆλ‹€.

    κΈ°λ³Έ κ°œλ…

    일반적인 ν•¨μˆ˜:

    function add(a, b, c) {
        return a + b + c;
    }
    
    add(1, 2, 3); // 6
    

    컀링된 ν•¨μˆ˜:

    function curriedAdd(a) {
        return function(b) {
            return function(c) {
                return a + b + c;
            };
        };
    }
    
    curriedAdd(1)(2)(3); // 6
    

    ν™”μ‚΄ν‘œ ν•¨μˆ˜λ‘œ κ°„λ‹¨ν•˜κ²Œ

    const curriedAdd = a => b => c => a + b + c;
    
    const result = curriedAdd(1)(2)(3); // 6
    

    λΆ€λΆ„ 적용 (Partial Application)

    컀링의 κ°€μž₯ 큰 μž₯점은 λΆ€λΆ„ 적용이 κ°€λŠ₯ν•˜λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€:

    const curriedAdd = a => b => c => a + b + c;
    
    // λΆ€λΆ„ 적용
    const add5 = curriedAdd(5);
    const add5And3 = add5(3);
    
    console.log(add5And3(2)); // 10 (5 + 3 + 2)
    console.log(add5(4)(1)); // 10 (5 + 4 + 1)
    

    μ‹€μš©μ μΈ μ˜ˆμ œλ“€

    1. λ¬Έμžμ—΄ 검사 ν•¨μˆ˜

    const hasSubstring = substring => str => str.includes(substring);
    
    const hasReact = hasSubstring('React');
    const hasVue = hasSubstring('Vue');
    
    console.log(hasReact('React 개발자')); // true
    console.log(hasVue('Vue.js ν”„λ ˆμž„μ›Œν¬')); // true
    
    // λ°°μ—΄μ—μ„œ ν™œμš©
    const frameworks = ['React', 'Angular', 'Vue', 'Svelte'];
    const reactItems = frameworks.filter(hasReact);
    console.log(reactItems); // ['React']
    

    2. μˆ˜ν•™ μ—°μ‚°

    const multiply = a => b => a * b;
    const divide = a => b => a / b;
    
    const double = multiply(2);
    const triple = multiply(3);
    const half = divide(1/2); // λ˜λŠ” multiply(0.5)
    
    console.log(double(5)); // 10
    console.log(triple(4)); // 12
    
    const numbers = [1, 2, 3, 4, 5];
    const doubled = numbers.map(double);
    console.log(doubled); // [2, 4, 6, 8, 10]
    

    3. 이벀트 ν•Έλ“€λŸ¬

    const log = level => message => {
        console.log(`[${level}] ${message}`);
    };
    
    const logError = log('ERROR');
    const logInfo = log('INFO');
    
    logError('λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° μ‹€νŒ¨'); // [ERROR] λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° μ‹€νŒ¨
    logInfo('μ„œλ²„ μ‹œμž‘λ¨'); // [INFO] μ„œλ²„ μ‹œμž‘λ¨
    

    4. API μš”μ²­

    const apiRequest = method => url => data => {
        return fetch(url, {
            method: method,
            headers: { 'Content-Type': 'application/json' },
            body: data ? JSON.stringify(data) : undefined
        });
    };
    
    const get = apiRequest('GET');
    const post = apiRequest('POST');
    const put = apiRequest('PUT');
    
    // μ‚¬μš©
    get('/api/users')();
    post('/api/users')({ name: 'κΉ€μ² μˆ˜', email: 'kim@example.com' });
    

    μžλ™ 컀링 ν•¨μˆ˜ λ§Œλ“€κΈ°

    function curry(fn) {
        return function curried(...args) {
            if (args.length >= fn.length) {
                return fn.apply(this, args);
            } else {
                return function(...nextArgs) {
                    return curried.apply(this, args.concat(nextArgs));
                };
            }
        };
    }
    
    // μ‚¬μš© 예
    function add(a, b, c) {
        return a + b + c;
    }
    
    const curriedAdd = curry(add);
    
    console.log(curriedAdd(1)(2)(3)); // 6
    console.log(curriedAdd(1, 2)(3)); // 6
    console.log(curriedAdd(1)(2, 3)); // 6
    console.log(curriedAdd(1, 2, 3)); // 6
    

    λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œμ˜ ν™œμš©

    Lodash

    const _ = require('lodash');
    
    const add = (a, b, c) => a + b + c;
    const curriedAdd = _.curry(add);
    
    console.log(curriedAdd(1)(2)(3)); // 6
    

    Ramda

    const R = require('ramda');
    
    const users = [
        { name: 'κΉ€μ² μˆ˜', age: 25 },
        { name: '이영희', age: 30 },
        { name: 'λ°•λ―Όμˆ˜', age: 35 }
    ];
    
    const getProperty = R.curry((prop, obj) => obj[prop]);
    const getName = getProperty('name');
    
    const names = users.map(getName);
    console.log(names); // ['κΉ€μ² μˆ˜', '이영희', 'λ°•λ―Όμˆ˜']
    

    컀링의 μž₯점

    1. μž¬μ‚¬μš©μ„±: νŠΉμ • λ§€κ°œλ³€μˆ˜κ°€ κ³ μ •λœ μƒˆλ‘œμš΄ ν•¨μˆ˜λ₯Ό μ‰½κ²Œ 생성
    2. 가독성: ν•¨μˆ˜μ˜ μ˜λ„κ°€ 더 λͺ…확해짐
    3. ν•¨μˆ˜ μ‘°ν•©: λ‹€λ₯Έ κ³ μ°¨ ν•¨μˆ˜λ“€κ³Ό μ‘°ν•©ν•˜κΈ° 쉬움
    4. λΆ€λΆ„ 적용: ν•„μš”ν•  λ•Œ 일뢀 인자만 λ¨Όμ € 적용 κ°€λŠ₯

    μ£Όμ˜μ‚¬ν•­

    1. μ„±λŠ₯: ν•¨μˆ˜ 호좜이 μ€‘μ²©λ˜λ―€λ‘œ μ•½κ°„μ˜ μ˜€λ²„ν—€λ“œ λ°œμƒ
    2. 디버깅: μŠ€νƒ νŠΈλ ˆμ΄μŠ€κ°€ λ³΅μž‘ν•΄μ§ˆ 수 있음
    3. 가독성: κ³Όλ„ν•˜κ²Œ μ‚¬μš©ν•˜λ©΄ 였히렀 μ½”λ“œλ₯Ό μ΄ν•΄ν•˜κΈ° μ–΄λ €μ›Œμ§ˆ 수 있음

    컀링은 ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ 핡심 κ°œλ… 쀑 ν•˜λ‚˜λ‘œ, μ½”λ“œμ˜ μž¬μ‚¬μš©μ„±κ³Ό λͺ¨λ“ˆμ„±μ„ 크게 ν–₯μƒμ‹œν‚¬ 수 μžˆλŠ” κ°•λ ₯ν•œ κΈ°λ²•μž…λ‹ˆλ‹€.

    Created Fri, 20 Jun 2025 14:11:00 +0900
  • 1. ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš© (Java 8+)

    기본적인 컀링 κ΅¬ν˜„

    import java.util.function.Function;
    
    public class CurryingExample {
        
        // 3개 인자λ₯Ό λ°›λŠ” ν•¨μˆ˜λ₯Ό 컀링
        public static Function<Integer, Function<Integer, Function<Integer, Integer>>> 
               curriedAdd() {
            return a -> b -> c -> a + b + c;
        }
        
        public static void main(String[] args) {
            // μ‚¬μš© 예
            Function<Integer, Function<Integer, Function<Integer, Integer>>> add = curriedAdd();
            
            int result1 = add.apply(1).apply(2).apply(3); // 6
            System.out.println(result1);
            
            // λΆ€λΆ„ 적용
            Function<Integer, Function<Integer, Integer>> add5 = add.apply(5);
            Function<Integer, Integer> add5And3 = add5.apply(3);
            
            int result2 = add5And3.apply(2); // 10
            System.out.println(result2);
        }
    }
    

    더 λ³΅μž‘ν•œ 예제 - λ¬Έμžμ—΄ 처리

    import java.util.function.Function;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class StringCurrying {
        
        // λ¬Έμžμ—΄ 포함 검사 ν•¨μˆ˜
        public static Function<String, Function<String, Boolean>> contains() {
            return substring -> str -> str.contains(substring);
        }
        
        // λ¬Έμžμ—΄ λ³€ν™˜ ν•¨μˆ˜
        public static Function<String, Function<String, String>> replace() {
            return target -> replacement -> str -> str.replace(target, replacement);
        }
        
        public static void main(String[] args) {
            List<String> frameworks = Arrays.asList("React", "Angular", "Vue", "Svelte");
            
            // λΆ€λΆ„ 적용된 ν•¨μˆ˜ 생성
            Function<String, Boolean> hasReact = contains().apply("React");
            
            // 슀트림과 ν•¨κ»˜ μ‚¬μš©
            List<String> reactItems = frameworks.stream()
                .filter(hasReact::apply)
                .collect(Collectors.toList());
            
            System.out.println(reactItems); // [React]
            
            // λ¬Έμžμ—΄ μΉ˜ν™˜
            Function<String, String> replaceJavaWithKotlin = 
                replace().apply("Java").apply("Kotlin");
            
            String result = replaceJavaWithKotlin.apply("I love Java programming");
            System.out.println(result); // I love Kotlin programming
        }
    }
    

    2. μ œλ„€λ¦­μ„ ν™œμš©ν•œ λ²”μš© 컀링 μœ ν‹Έλ¦¬ν‹°

    import java.util.function.*;
    
    public class CurryUtil {
        
        // 2개 인자 ν•¨μˆ˜ 컀링
        public static <A, B, R> Function<A, Function<B, R>> 
               curry(BiFunction<A, B, R> function) {
            return a -> b -> function.apply(a, b);
        }
        
        // 3개 인자 ν•¨μˆ˜λ₯Ό μœ„ν•œ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€
        @FunctionalInterface
        public interface TriFunction<A, B, C, R> {
            R apply(A a, B b, C c);
        }
        
        // 3개 인자 ν•¨μˆ˜ 컀링
        public static <A, B, C, R> Function<A, Function<B, Function<C, R>>> 
               curry(TriFunction<A, B, C, R> function) {
            return a -> b -> c -> function.apply(a, b, c);
        }
        
        public static void main(String[] args) {
            // BiFunction 컀링
            BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
            Function<Integer, Function<Integer, Integer>> curriedMultiply = curry(multiply);
            
            Function<Integer, Integer> double_ = curriedMultiply.apply(2);
            System.out.println(double_.apply(5)); // 10
            
            // TriFunction 컀링
            TriFunction<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;
            Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedAdd3 = curry(add3);
            
            int result = curriedAdd3.apply(1).apply(2).apply(3);
            System.out.println(result); // 6
        }
    }
    

    3. 클래슀 기반 κ΅¬ν˜„

    public class MathOperations {
        
        public static class CurriedAdd {
            private final int first;
            
            private CurriedAdd(int first) {
                this.first = first;
            }
            
            public static CurriedAdd of(int first) {
                return new CurriedAdd(first);
            }
            
            public CurriedAddSecond with(int second) {
                return new CurriedAddSecond(first, second);
            }
            
            public class CurriedAddSecond {
                private final int second;
                
                private CurriedAddSecond(int first, int second) {
                    this.second = second;
                }
                
                public int and(int third) {
                    return first + second + third;
                }
            }
        }
        
        public static void main(String[] args) {
            int result = CurriedAdd.of(1).with(2).and(3); // 6
            System.out.println(result);
            
            // λΆ€λΆ„ 적용
            CurriedAdd add5 = CurriedAdd.of(5);
            CurriedAdd.CurriedAddSecond add5And3 = add5.with(3);
            
            int result2 = add5And3.and(2); // 10
            System.out.println(result2);
        }
    }
    

    4. μ‹€μš©μ μΈ 예제 - HTTP ν΄λΌμ΄μ–ΈνŠΈ

    import java.util.function.Function;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.net.URI;
    
    public class CurriedHttpClient {
        
        public static Function<String, Function<String, Function<String, HttpRequest>>> 
               httpRequest() {
            return method -> url -> body -> {
                HttpRequest.Builder builder = HttpRequest.newBuilder()
                    .uri(URI.create(url));
                
                switch (method.toUpperCase()) {
                    case "GET":
                        builder.GET();
                        break;
                    case "POST":
                        builder.POST(HttpRequest.BodyPublishers.ofString(body));
                        break;
                    case "PUT":
                        builder.PUT(HttpRequest.BodyPublishers.ofString(body));
                        break;
                }
                
                return builder.build();
            };
        }
        
        public static void main(String[] args) {
            // λΆ€λΆ„ 적용된 ν•¨μˆ˜λ“€ 생성
            Function<String, Function<String, HttpRequest>> get = 
                httpRequest().apply("GET");
            Function<String, Function<String, HttpRequest>> post = 
                httpRequest().apply("POST");
            
            // μ‚¬μš©
            HttpRequest getRequest = get.apply("https://api.example.com/users").apply("");
            HttpRequest postRequest = post.apply("https://api.example.com/users")
                .apply("{\"name\":\"κΉ€μ² μˆ˜\"}");
            
            System.out.println("GET: " + getRequest.uri());
            System.out.println("POST: " + postRequest.uri());
        }
    }
    

    5. Vavr 라이브러리 μ‚¬μš©

    Vavr(이전 Javaslang)λŠ” μžλ°”μ—μ„œ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ 더 μ‰½κ²Œ ν•  수 있게 ν•΄μ£ΌλŠ” λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€:

    Created Fri, 20 Jun 2025 14:11:00 +0900