item 79 incheol - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

Effective Java 3e ์•„์ดํ…œ 79๋ฅผ ์š”์•ฝํ•œ ๋‚ด์šฉ ์ž…๋‹ˆ๋‹ค.

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

๋‹ค์Œ์€ ์–ด๋–ค ์ง‘ํ•ฉ(Set)์„ ๊ฐ์‹ผ ๋ž˜ํผ ํด๋ž˜์Šค์ด๊ณ , ์ด ํด๋ž˜์Šค์˜ ํด๋ผ์ด์–ธํŠธ๋Š” ์ง‘ํ•ฉ์— ์›์†Œ๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

public class ObservableSet<E> extends ForwardingSet<E> {

    public ObservableSet(Set<E> set) {
        super(set);
    }

    private final List<SetObserver<E>> observers = new ArrayList<>();

    public void addObserver(SetObserver<E> observer) {
        synchronized (observers) {
            observers.add(observer);
        }
    }
    
    public boolean removeObserver(SetObserver<E> observer) {
        synchronized (observers) {
            return observers.remove(observer);
        }
    }
    
    private void notifyElementAdded(E element) {
        synchronized (observers) {
            for(SetObserver<E> observer : observers) {
                observer.added(this, element);
            }
        }
    }
    
    @Override
    public boolean add(E element) {
        boolean added = super.add(element);
        if(added) {
            notifyElementAdded(element);
        }
        return added;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean result = false;
        for (E element : c) {
            result |= add(element); //notifyElementAdded๋ฅผ ํ˜ธ์ถœ
        }
        return result;
    }
}

๊ด€์ฐฐ์ž๋“ค์€ addObserver์™€ removeObserver ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ๊ตฌ๋…์„ ์‹ ์ฒญํ•˜๊ฑฐ๋‚˜ ํ•ด์ง€ํ•œ๋‹ค. ๋ˆˆ์œผ๋กœ ๋ณด๊ธฐ์— ObservableSet์€ ์ž˜ ๋™์ž‘ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

๋‹ค์Œ ํ”„๋กœ๊ทธ๋žจ์€ 0๋ถ€ํ„ฐ 99๊นŒ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. ํ‰์ƒ์‹œ์—๋Š” ์•ž์„œ์™€ ๊ฐ™์ด ์ง‘ํ•ฉ์— ์ถ”๊ฐ€๋œ ์ •์ˆซ๊ฐ’์„ ์ถœ๋ ฅํ•˜๋‹ค๊ฐ€, ๊ทธ ๊ฐ’์ด 23์ด๋ฉด ์ž๊ธฐ ์ž์‹ ์„ ์ œ๊ฑฐํ•˜๋Š” ๊ด€์ฐฐ์ž๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์ž.

public static void main(String[] args) {
	ObservableSet<Integer> set = new ObservableSet<>(New HashSet<>());
	
	set.addObserver(new SetObserver<Integer>() {
		public void added(ObservableSet<Integer> s, Integer e) {
			System.out.println(e);
			if (e == 23) s.removeObserver(this);
		}
	});

	for (int i = 0; i < 100; i++) 
		set.add(i);
}

์ด ํ”„๋กœ๊ทธ๋žจ์€ 23๊นŒ์ง€ ์ถœ๋ ฅํ•œ ๋‹ค์Œ ConcurrentModificationException์„ ๋˜์ง„๋‹ค. ๊ด€์ฐฐ์ž์˜ added ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด ์ผ์–ด๋‚œ ์‹œ์ ์ด notifyElementAdded๊ฐ€ ๊ด€์ฐฐ์ž๋“ค์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ˆœํšŒํ•˜๋Š” ๋„์ค‘์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. added ๋ฉ”์„œ๋“œ๋Š” ObservableSet์˜ removeObserver ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์ด ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์‹œ observers.remove ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋ฆฌ์ŠคํŠธ์—์„œ ์›์†Œ๋ฅผ ์ œ๊ฑฐํ•˜๋ ค ํ•˜๋Š”๋ฐ, ๋งˆ์นจ ์ง€๊ธˆ์€ ์ด ๋ฆฌ์ŠคํŠธ๋ฅผ ์ˆœํšŒํ•˜๋Š” ๋„์ค‘์ด๋‹ค. ์ฆ‰ ํ—ˆ์šฉํ•˜๋Š” ๋™์ž‘์ด๋‹ค.

์ด๋ฒˆ์—” ๋‹ค๋ฅธ ์˜ˆ์™ธ๋ฅผ ์‹œ๋„ํ•ด๋ณด์ž.

๊ตฌ๋… ํ•ด์ง€ ํ•˜๋Š” ๊ด€์ฐฐ์ž๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ removeObserver๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์‹คํ–‰์ž ์„œ๋น„์Šค(ExecutorService, ์•„์ดํ…œ 80)๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œํ•œํ…Œ ๋ถ€ํƒํ•  ๊ฒƒ์ด๋‹ค.

set.addObserver(new SetObserver<Integer>() {
    public void added(ObservableSet<Integer> s, Integer e) {
        System.out.println(e);
        if (e == 23) {
            ExecutorService exec = Executors.newSingleThreadExecutor();
            try {
                exec.submit(() -> s.removeObserver(this)).get();
            } catch (ExecutionException | InterruptedException ex) {
                throw new AssertionError(ex);
            } finally {
                exec.shutdown();
            }
        }
    }
});

์ด ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•˜๋ฉด ์˜ˆ์™ธ๋Š” ๋‚˜์ง€ ์•Š์ง€๋งŒ ๊ต์ฐฉ์ƒํƒœ์— ๋น ์ง„๋‹ค. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๊ฐ€ s.removeObserver๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ด€์ฐฐ์ž๋ฅผ ์ž ๊ทธ๋ ค ์‹œ๋„ํ•˜์ง€๋งŒ ๋ฝ์„ ์–ป์„ ์ˆ˜ ์—†๋‹ค. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ด๋ฏธ ๋ฝ์„ ์ฅ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ์™€ ๋™์‹œ์— ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ด€์ฐฐ์ž๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ๋งŒ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘์ด๋‹ค. ๋ฐ”๋กœ ๊ต์ฐฉ์ƒํƒœ๋‹ค!

์‚ฌ์‹ค ๊ด€์ฐฐ์ž๊ฐ€ ์ž์‹ ์„ ๊ตฌ๋… ํ•ด์ง€ ํ•˜๋Š” ๋ฐ ๊ตณ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๋ฅผ ์ด์šฉํ•  ์ด์œ ๊ฐ€ ์—†์œผ๋‹ˆ ์ข€ ์–ต์ง€์Šค๋Ÿฌ์šด ์˜ˆ์ง€๋งŒ, ์‹ค์ œ ์‹œ์Šคํ…œ์—์„œ๋„ ๋™๊ธฐํ™”๋œ ์˜์—ญ ์•ˆ์—์„œ ์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ต์ฐฉ์ƒํƒœ์— ๋น ์ง€๋Š” ์‚ฌ๋ก€๋Š” ์ž์ฃผ ์žˆ๋‹ค.

์™ธ๊ณ„์ธ ๋ฉ”์„œ๋“œ๋ž€ ๋™๊ธฐํ™”๋œ ์˜์—ญ ์•ˆ์—์„œ ์žฌ์ •์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋„˜๊ฒจ์ค€ ํ•จ์ˆ˜ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ๋œปํ•œ๋‹ค

๋‹คํ–‰ํžˆ ์ด๋Ÿฐ ๋ฌธ์ œ๋Š” ๋Œ€๋ถ€๋ถ„ ์–ด๋ ต์ง€ ์•Š๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. notifyElementAdded ๋ฉ”์„œ๋“œ ์—์„œ ๊ด€์ฐฐ์ž ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณต์‚ฌํ•ด ์“ฐ๋ฉด ๋ฝ ์—†์ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ˆœํšŒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฐฉ์‹์„ ์ ์šฉํ•˜๋ฉด ์•ž์„œ์˜ ๋‘ ์˜ˆ์ œ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ๊ณผ ๊ต์ฐฉ์ƒํƒœ ์ฆ์ƒ์ด ์‚ฌ๋ผ์ง„๋‹ค.

private void notifyElementAdded(E element) {
	List<SetObserver<E>> snapshot = null;
	synchronized(observers) {
		snapshot = new ArrayList<>(observers);
	}
	for (SetObserver<E> observer : snapshot)
		observer.added(this, element);
}

์‚ฌ์‹ค ์™ธ๋ถ€ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ ๋™๊ธฐํ™” ๋ธ”๋ก ๋ฐ”๊นฅ์œผ๋กœ ์˜ฎ๊ธฐ๋Š” ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ์ž๋ฐ”์˜ ๋™์‹œ์„ฑ ์ปฌ๋ ‰์…˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ CopyOnWriteArrayList๊ฐ€ ์ •ํ™•ํžˆ ์ด ๋ชฉ์ ์œผ๋กœ ํŠน๋ณ„ํžˆ ์„ค๊ณ„๋œ ๊ฒƒ์ด๋‹ค.

private final List<SetObserver<E>> observers = new CopyOnWriteArrayList<>();

public void addObserver(SetObserver<E> observer) {
	observers.add(observer);
}

public boolean removeObserver(SetObserver<E> observer) {
	return observers.remove(observer);
}

private void notifyElementAdded(E element) {
	for (SetObserver<E> observer : observers)
		observer.added(this, element);
}

๋™๊ธฐํ™” ์˜์—ญ ์•ˆ์—์„œ ํ˜ธ์ถœ๋œ๋‹ค๋ฉด ๊ทธ๋™์•ˆ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋Š” ๋ณดํ˜ธ๋œ ์ž์›์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋Œ€๊ธฐ ํ•ด์•ผ๋งŒ ํ•œ๋‹ค. ๊ธฐ๋ณธ ๊ทœ์น™์€ ๋™๊ธฐํ™” ์˜์—ญ์—์„œ๋Š” ๊ฐ€๋Šฅํ•œ ํ•œ ์ผ์„ ์ ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ž๋ฐ”์˜ ๋™๊ธฐํ™” ๋น„์šฉ์€ ๋น ๋ฅด๊ฒŒ ๋‚ฎ์•„์ ธ ์™”์ง€๋งŒ, ๊ณผ๋„ํ•œ ๋™๊ธฐํ™”๋ฅผ ํ”ผํ•˜๋Š” ์ผ์€ ์˜คํžˆ๋ ค ๊ณผ๊ฑฐ ์–ด๋Š ๋•Œ๋ณด๋‹ค ์ค‘์š”ํ•˜๋‹ค. ๊ณผ๋„ํ•œ ๋™๊ธฐํ™”๊ฐ€ ์ดˆ๋ž˜ํ•˜๋Š” ์ง„์งœ ๋น„์šฉ์€ ๋ฝ์„ ์–ป๋Š” ๋ฐ ๋“œ๋Š” CPU ์‹œ๊ฐ„์ด ์•„๋‹ˆ๋‹ค. ๋ฐ”๋กœ ๊ฒฝ์Ÿ ํ•˜๋Š๋ผ ๋‚ญ๋น„ํ•˜๋Š” ์‹œ๊ฐ„, ์ฆ‰ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•  ๊ธฐํšŒ๋ฅผ ์žƒ๊ณ , ๋ชจ๋“  ์ฝ”์–ด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ๋ณด๊ธฐ ์œ„ํ•œ ์ง€์—ฐ ์‹œ๊ฐ„์ด ์ง„์งœ ๋น„์šฉ์ด๋‹ค.

๊ฐ€๋ณ€ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑ ํ•˜๋ ค๊ฑฐ๋“  ๋‹ค์Œ ๋‘ ์„ ํƒ์ง€ ์ค‘ ํ•˜๋‚˜๋ฅผ ๋”ฐ๋ฅด์ž.

  1. **๋™๊ธฐํ™”๋ฅผ ์ „ํ˜€ ํ•˜์ง€ ๋ง๊ณ , ๊ทธ ํด๋ž˜์Šค๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ํด๋ž˜์Šค๊ฐ€ ์™ธ๋ถ€์—์„œ ์•Œ์•„์„œ ๋™๊ธฐํ™”ํ•˜๊ฒŒ ํ•˜์ž**
  2. ๋™๊ธฐํ™”๋ฅผ ๋‚ด๋ถ€์—์„œ ์ˆ˜ํ–‰ํ•ด ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•œ ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค์ž. (์•„์ดํ…œ 82)

java.util์€ (์ด์ œ ๊ตฌ์‹์ด ๋œ Vector์™€ HashTable์„ ์ œ์™ธํ•˜๊ณ ) ์ฒซ ๋ฒˆ์งธ ๋ฐฉ์‹์„ ์ทจํ–ˆ๊ณ , java.util.concurrent๋Š” ๋‘ ๋ฒˆ์งธ ๋ฐฉ์‹์„ ์ทจํ–ˆ๋‹ค(์•„์ดํ…œ 81)

์ž๋ฐ”๋„ ์ดˆ์ฐฝ๊ธฐ์—๋Š” ์ด ์ง€์นจ์„ ๋”ฐ๋ฅด์ง€ ์•Š์€ ํด๋ž˜์Šค๊ฐ€ ๋งŽ์•˜๋‹ค. ์˜ˆ์ปจ๋Œ€ StringBuffer ์ธ์Šคํ„ด์Šค๋Š” ๊ฑฐ์˜ ํ•ญ์ƒ ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ ์‚ฌ์šฉํ–ˆ์Œ์—๋„ ๋‚ด๋ถ€์ ์œผ๋กœ ๋™๊ธฐํ™”๋ฅผ ์ˆ˜ํ–‰ํ–ˆ๋‹ค. (๋’ค๋Šฆ๊ฒŒ StringBuilder๊ฐ€ ๋“ฑ์žฅํ•œ ์ด์œ ์ด๊ธฐ๋„ ํ•˜๋‹ค)

ํด๋ž˜์Šค๋ฅผ ๋‚ด๋ถ€์—์„œ ๋™๊ธฐํ™” ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค๋ฉด, ๋ฝ ๋ถ„ํ• (lock splitting), ๋ฝ ์ŠคํŠธ๋ผ์ดํ•‘(lock striping), ๋น„์ฐจ๋‹จ ๋™์‹œ์„ฑ ์ œ์–ด(nonblocking concurrecy control) ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋ฒ•์„ ๋™์›ํ•ด ๋™์‹œ์„ฑ์„ ๋†’์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ํ˜ธ์ถœํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์  ํ•„๋“œ๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค๋ฉด ๊ทธ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ ๋™๊ธฐํ™”ํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๋กœ ๋ณต์ œ๋ผ ๊ตฌ๋™๋˜๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ๋‹ค๋ฅธ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฑธ ๋ง‰์„ ์ˆ˜ ์—†์œผ๋‹ˆ ์™ธ๋ถ€์—์„œ ๋™๊ธฐํ™”ํ•  ๋ฐฉ๋ฒ•์ด ์—†๋‹ค.

์ •๋ฆฌ

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

โš ๏ธ **GitHub.com Fallback** โš ๏ธ