page-cover

title

ํ”„๋ฆฌ์ฝ”์Šค ๊ด€๋ จํ•œ ์œ ํ‹ธ ์†Œ์Šค๋Š” ์—ฌ๊ธฐ์— ์—…๋กœ๋“œํ•˜๊ณ  ์žˆ๋‹ค. ํ•„์š”ํ•˜๋ฉด ์ฐธ๊ณ ํ•˜๋„๋ก ํ•˜์ž.

์ตœ๊ทผ์— ๋‚ด๊ฐ€ ์ž‘์„ฑํ•˜๋Š” ์ฝ”๋“œ๋“ค์ด ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€ํ–ฅํ•˜๋Š” ๊ฒƒ์„ ๋ณด๋ฉด์„œ, ๋‚˜์ค‘์— ๋น„๋™๊ธฐ์ ์ธ ์ž‘์—…๋“ค์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์™„์ „ ํ—ค๋งค์ง€๋Š” ์•Š๊ฒ ๋‹ค ์‹ถ๋‹ค๊ฐ€๋„, Java์—์„œ ๋žŒ๋‹ค๋Š” ๋ณ„๋„์˜ ์ต๋ช… ํด๋ž˜์Šค์˜ ํ•จ์ˆ˜๋กœ ์„ ์–ธ๋˜์–ด ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์Šค๋ ˆ๋“œ ๋ฉ”๋ชจ๋ฆฌ ๋คํ”„๋กœ ์ €์ˆ˜์ค€ ๋””๋ฒ„๊น…์„ ์‹œ๋„ํ•  ๋•Œ ํฐ ๋ฐฉํ•ด๊ฐ€ ๋œ๋‹ค๋Š” ์ด์•ผ๊ธฐ๋„ ๋“ค์–ด์„œ ์กฐ์‹ฌ์Šค๋Ÿฝ๊ธฐ๋„ ํ•˜๋‹ค. (Kotlin์œผ๋กœ ๋„˜์–ด๊ฐ€์•ผ ํ•  ๋•Œ์ธ๊ฐ€?)

์ด๋ฒˆ ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ž‘์—…์—์„œ ์–ป์€ ๊ฒƒ์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€์ด๋‹ค. ์ฒซ์งธ๋Š” javadocs์— ๋Œ€ํ•œ ์ง€์‹์ด ํ•˜๋‚˜ ๋Š˜์—ˆ๋‹ค๋Š” ๊ฒƒ, ๋‘˜์งธ๋Š” '์ด ํŽธํ•˜๊ณ  ์ข‹์€ ๊ฑธ ์™œ ์ง„์ž‘ ์•ˆ ํ•˜๊ณ  ์žˆ์—ˆ์ง€?' ๋ผ๋Š” ๊นจ๋‹ฌ์Œ, ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์€ ์ง€์†์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜๋Š” ์†Œ์Šค์˜ ๋ฐœ์ „ ๊ฐ€๋Šฅ์„ฑ์ด๋‹ค.

javadocs

์œ ํ‹ธ๋ฆฌํ‹ฐ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ž์„ธํžˆ ์ž‘์„ฑํ•˜๋ ค๋‹ค๋ณด๋‹ˆ javadocs ๋ฌธ๋ฒ•์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ด€์‹ฌ์ด ๊ฐ”๋‹ค. ๊ธฐ์กด์— ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋œฏ์–ด๋ณด๋ฉด @see, {@link} ๋˜๋Š” <pre>์™€ {@code}์˜ ์กฐํ•ฉ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์— ์šฐ์—ฐํžˆ {@snippet}๋„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.1

'ํ•œ ๋‹ฌ ํ›„์— ๋‹ค์‹œ ๋ณด๋”๋ผ๋„ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก' ์ž‘์„ฑํ•˜๋ ค ๋…ธ๋ ฅํ–ˆ๋Š”๋ฐ ์ƒ๊ฐ๋ณด๋‹ค ์‰ฝ์ง€ ์•Š๋‹ค. IDE์—์„œ ๋ณด๋ฉด ์ข€ ๋” ๊น”๋”ํ•ด๋ณด์ด๊ธด ํ•˜๋Š”๋ฐ, ์žฅํ™ฉํ•˜๊ฒŒ ์“ธ๋งŒํผ ๋Œ€๋‹จํ•œ ๋‚ด์šฉ์ด ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์€ ์•„๋‹ˆ์ง€๋งŒ, ๊ทธ๋ ‡๋‹ค๊ณ  ์ค„๊ธ€๋งŒ ๋‚จ๊ฒจ๋‘์ž๋‹ˆ ์ฒ˜์Œ์˜ ๋ชฉํ‘œ์— ๋ถ€ํ•ฉํ•˜์ง€ ์•Š์•„์„œ ๊ณ ๋ฏผ์„ ๋งŽ์ด ํ–ˆ๋‹ค. ๋ณดํ†ต ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณด๋ฉด ํ™œ์šฉ ์˜ˆ์ œ ํ•˜๋‚˜์”ฉ์€ ๊ผญ ๊ฐ–์ถ”๊ณ  ์žˆ์–ด์„œ ๋‚˜๋„ ๊ฐ„๋‹จํžˆ ์ž‘์„ฑํ•ด๋ดค๋‹ค. gist ์†Œ์Šค ๋งํฌ 2

utility-javadocs.png

์œ ํ‹ธ๋ฆฌํ‹ฐํ™”

์–ด๋งˆ์–ด๋งˆํ•˜๊ฒŒ ๊ฑฐ๋Œ€ํ•œ ์˜คํ”ˆ์†Œ์Šค ์ƒํƒœ๊ณ„๋Š” ์ด๋ฏธ ์šฐ๋ฆฌ ์‚ถ ๊นŠ์€ ๊ณณ๊นŒ์ง€ ์นจํˆฌํ•œ ์ง€ ์˜ค๋ž˜๋‹ค. ์ž‘์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ์†Œ์Šค์กฐ์ฐจ ๊ทธ ํŒŒ๊ธ‰๋ ฅ์€ ๊ฑฐ๋Œ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค. 1. kik-left-pad-and-npm 2. wiki/Npm_left-pad_incident ์ž‘์€ ํ•จ์ˆ˜๋“ค์ด ๋งž๋ฌผ๋ ค ๊ฑฐ๋Œ€ํ•œ ์‹œ์Šคํ…œ์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฉ‹์ง„ ๊ฒฝ์šฐ๋„ ์žˆ์ง€๋งŒ, ๊ฒฐ๊ตญ ์ ์žฌ์ ์†Œ์— ์•Œ์•„์„œ ์ž˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ฐธ ์–ด๋ ค์šด ์ผ์ธ ๊ฒƒ ๊ฐ™๋‹ค. ํƒ€์ธ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋”์šฑ.

๊ทธ๋Ÿฐ ์ ์—์„œ ๋‚ด๊ฐ€ ๋‚˜๋งŒ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๊ฐœ์ธ์˜ ์ƒ์‚ฐ์„ฑ ์ธก๋ฉด์—์„œ ๋งค์šฐ ํšจ๊ณผ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๋”ฑ ๋‚˜ ์ •๋„์˜ ์ˆ˜์ค€์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ์ด ๋งŒ๋“  ์ด ์ฝ”๋“œ๋Š”, ๋‚ด๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•œ ๋ฐฉ์‹์œผ๋กœ ์ž˜ ๊ตฌํ˜„ํ•ด๋‘์—ˆ์œผ๋‹ˆ๊นŒ.

๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ Parser๋Š” ๊ธฐ์กด String.split์—์„œ ํ—ˆ์šฉํ•˜๋Š” trailingDelimiter์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๋ฅผ ํฌํ•จํ•ด์„œ ๋ณด๋‹ค ์—„๊ฒฉํ•œ ํ˜•์‹ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋˜, parse() ๋ฉ”์„œ๋“œ์˜ ์˜ค๋ฒ„๋กœ๋”ฉ์œผ๋กœ ๋ฐ์ดํ„ฐ์˜ ๋ณ€ํ™˜ ํ๋ฆ„์ด ์œ ์—ฐํ•˜๊ฒŒ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋„๋ก ์‹ ๊ฒฝ์ผ๋‹ค.

package example.util;

import java.util.List;
import java.util.regex.Pattern;

/**
 * ์ฃผ์–ด์ง„ ๊ตฌ๋ถ„์ž๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ˆœ์„œ๋ฅผ ์ง€ํ‚ค๋ฉฐ ํ† ํฐ์„ ๋ถ„ํ• ํ•ฉ๋‹ˆ๋‹ค.
 * <pre>{@code
 * var parser = new Parser(',');
 * console.printLine("๊ฒฝ์ฃผํ•  ์ž๋™์ฐจ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”.(์ด๋ฆ„์€ ์‰ผํ‘œ(,) ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„)");
 * var carNames = parser.parse(console.readLine());
 * }</pre>
 * 2๊ฐœ ์ด์ƒ์˜ ๊ตฌ๋ถ„์ž๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ๊ตฌ๋ถ„์ž๋ฅผ ๋‚˜์—ดํ•ด์„œ ์ƒ์„ฑํ•˜์„ธ์š”.
 * {@snippet :new Parser(',', ':').parse("1,2:3"); // {1, 2, 3} // @highlight regex="'.'" }
 */
public final class Parser {
    private final Pattern pattern;

    public Parser(int... delimiter) {
        String quotes = java.util.Arrays.stream(delimiter)
                .mapToObj(Parser::toRegexQuote)
                .collect(java.util.stream.Collectors.joining());
        pattern = Pattern.compile(String.format("[%s]", quotes));
    }

    private static String toRegexQuote(int c) {
        return Pattern.quote(Character.toString(c));
    }

    public List<String> parse(String input) {
        if (input.isEmpty()) {
            return List.of();
        }
        checkTrailingDelimiter(input);
        return List.of(pattern.split(input));
    }

    public <T> List<T> parse(String input, java.util.function.Function<String, T> converter) {
        return parse(input).stream().map(converter).toList();
    }

    private void checkTrailingDelimiter(String input) throws IllegalArgumentException {
        var s = String.valueOf(input.charAt(input.length() - 1));
        if (pattern.matcher(s).matches()) {
            throw new IllegalArgumentException();
        }
    }
}

์ง€์†์ ์ธ ๊ด€๋ฆฌ ๋ฐ ๊ฐœ๋ฐœ

๋ฐ”๊ฟ€ ์ผ์ด ์—†์„๊ฑฐ๋ผ ์ƒ๊ฐํ–ˆ๋˜ ๊ณณ์„ ๋ณ€๊ฒฝํ•  ์ผ์ด ์ƒ๊ธฐ๊ณ , ์™„๋ฒฝํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ ์ฝ”๋“œ์— ๊ฒฐ์ ์ด ๋ณด์ด๋Š” ๋•Œ๊ฐ€ ์ƒ๊ธด๋‹ค.

๋‚ด๊ฐ€ ValueRules๋ผ๋Š” ์œ ํ‹ธ์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋œ ๊ณ„๊ธฐ๋Š”, ์ž์ฃผ ํ•„์š”ํ•˜๊ฒŒ ๋˜๋Š” ํŠน์ •ํ•œ ๊ฒ€์ฆ ํŒจํ„ด๋“ค์„ '๋”ธ๊น'ํ•ด์„œ ํ•ด๊ฒฐํ•˜๊ณ  ์‹ถ๋‹ค๋Š” ์–„ํŒํ•œ ๋งˆ์Œ ๋•Œ๋ฌธ์ด์—ˆ๋‹ค. ์กฐ๊ฑด๋ฌธ๊ณผ try-catch ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๋ฉด ๋˜๋Š”๋ฐ ๊ทธ๊ฒŒ ๊ท€์ฐฎ์•˜๋‚˜๋ณด๋‹ค ๐Ÿ˜‚ ํ•˜์ง€๋งŒ ๋ง‰์ƒ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๋‹ˆ ์•„์ด๋””์–ด๊ฐ€ ์ƒ˜์†Ÿ์•„, ์ œ๋„ค๋ฆญ๊ณผ ํ•จ์ˆ˜ํ˜• ์ธ์ž๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ชจ๋“  ํƒ€์ž…์„ ์ง€์›ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ดˆ๊ธฐ ์•„์ด๋””์–ด๋Š” ๊ทธ๋ƒฅ RuntimeException์„ ์ƒ์†ํ•˜๋Š” ์ปค์Šคํ…€ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๊ฒƒ์ด์—ˆ๋Š”๋ฐ, ๋ง‰์ƒ ์ œ๋„ค๋ฆญ์œผ๋กœ ๋งŒ๋“ค๊ณ ๋ณด๋‹ˆ ์œ ํ‹ธ์ด๋ผ null ๊ฒ€์‚ฌ๋„ ํ•ด์•ผํ•  ๊ฒƒ ๊ฐ™๊ณ , Objects.requireNonNull์„ ํ™œ์šฉํ•ด์„œ NullPointerExceptio๋„ ๋˜์ง€๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค. ๋‹น์‹œ์—๋Š” ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ๋‘ ๊ฐœ๋ฅผ ๊ตฌ๋ถ„ํ•ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

@FunctionalInterface
public interface CanValidate<T> {
	void validate(T value) throws NullPointerException, InvalidValueException;
}

public record OfString(java.util.function.Predicate<String> assertion) implements CanValidate<String> {
	@Override
	public void validate(String value) throws NullPointerException, InvalidValueException {
	java.util.Objects.requireNonNull(value);
	InvalidValueException.throwsIf(assertion.test(value), this.getClass());
	}
}

ํ•˜์ง€๋งŒ ๋ง‰์ƒ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•ด๋ณด๋‹ˆ ๋ถˆํŽธํ•œ ์ ์ด ๋А๊ปด์กŒ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ ์ˆ˜์ค€์—์„œ ๊ฐ’์˜ ๊ฒ€์ฆ ์‹คํŒจ์— ๋”ฐ๋ฅธ ์˜ˆ์™ธ๋ฅผ IllegalArgumentException์œผ๋กœ ๊ณ ์ •ํ•˜๊ณ  catchํ•ด์„œ ์žฌ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค๋ณด๋‹ˆ ๋‹ค๋ฅธ ์˜ˆ์™ธ์—๋Š” catch ๊ตฌ๋ฌธ์„ ์ถ”๊ฐ€ํ•ด์•ผ๋งŒ ํ–ˆ๋‹ค. ํŽธํ•˜๋ ค๊ณ  ์‚ฌ์šฉํ•˜๋Š” ์œ ํ‹ธ์ด ์˜คํžˆ๋ ค catch ๊ตฌ๋ฌธ์„ ๊ธธ๊ฒŒ ๋งŒ๋“  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ ์˜ˆ์™ธ๋ฅผ custom ์˜ˆ์™ธ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•˜๊ณ , checkedException์œผ๋กœ ๋ฐ”๊ฟจ๋‹ค. ๋‚˜๋Š” ์ด๋Ÿฐ ๋ฐฉ์‹์ด ๋” ํŽธํ•˜๊ฒŒ ๋А๊ปด์กŒ๋‹ค.

public record Stock(String name, int price, int quantity) {
    private static final ValueRules.OfInteger priceRule = new ValueRules.OfIntegerinteger -> integer >= 100 && integer % 10 == 0);

    public Stock {
        try {
            ValueRules.OF_NON_BLANK.validate(name);
            priceRule.validate(price);
            ValueRules.OF_POSITIVE.validate(quantity);
        } catch (InvalidValueException e) {
            throw GlobalExceptions.INVALID_ARGUMENTS.exception();
        }
    }
}

Java API ๋Œ€๋‹ค์ˆ˜๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ try-catch๋ฅผ ๊ฐ•์ œํ•˜์ง€ ์•Š๋„๋ก ๋Ÿฐํƒ€์ž„ ์˜ˆ์™ธ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. ์ตœ๊ทผ์—๋Š” Reflection API ์ •๋„์—์„œ๋งŒ checkedException์„ ๋ณธ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ๋”์šฑ ์ปค์Šคํ…€ ์œ ํ‹ธ์ด ์ค‘์š”ํ•˜๊ฒŒ ๋А๊ปด์ง„๋‹ค. ์ฝ”๋“œ๊ฐ€ ๋ถ„๋ช…ํ•˜๊ฒŒ ์ž˜๋ชป๋œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋ฉด, ํ—ˆ์šฉ๋œ ์ž์œ  ์•ˆ์—์„œ ๋‚ด๊ฐ€ ์ถ”๊ตฌํ•˜๋Š” ๋ฐฉํ–ฅ์˜ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ์— ํฐ ๋„์›€์ด ๋ ํ…Œ๋‹ˆ๊นŒ.

Footnotes

  1. ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์˜ ์†Œ์Šค์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์ž๋™์œผ๋กœ ๋งํฌ๋กœ ๋งŒ๋“ค๊ณ , ์™ธ๋ถ€ ํŒŒ์ผ์— ์ž‘์„ฑํ•œ ์ฝ”๋“œ ๋‚ด์šฉ์„ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๊ณ , ๋ถˆ๋Ÿฌ์˜จ ํ›„์—๋„ ๊ฐ•์กฐํ•˜๋Š” ๋“ฑ์˜ ์ˆ˜์ •์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•œ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์„ค๋ช…์„ ์ฐธ๊ณ ํ•˜์ž. โ†ฉ

  2. ์ค‘๊ฐ„์— {@snippet}๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” code-snippet ํด๋”๋ฅผ ํ™œ์šฉํ•ด์„œ ์ฝ”๋“œ๋ฅผ region ๋ณ„๋กœ ๊ฐ€์ ธ์˜ค๋Š” ์‹์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ–ˆ์—ˆ๋Š”๋ฐ, ๊ดœํžˆ ํŒŒ์ผ์ด ๋ถ„๋ฆฌ๋˜๋Š” ๊ฒƒ๋„ ์ข‹์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™์•„์„œ ๋‹ค์‹œ ํ•˜๋‚˜๋กœ ํ•ฉ์ณค๋‹ค. โ†ฉ