본문 바로가기
Study/클린코드

[클린코드] 2장. 의미 있는 이름

by Nahwasa 2022. 12. 14.

스터디 메인 페이지

목차

    - ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에서 말하고자 하는 바와 다를 수 있습니다.

    - 모든 이미지의 출처는 클린 코드(로버트 C. 마틴 저) 책 입니다.

     


     

    2장 의미 있는 이름

    의도를 분명히 밝혀라

     의도가 분명하게 이름을 지어라. 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.

     

     변수, 함수, 클래스의 이름은 다음의 질문에 답해야 한다. 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.

    • 존재 이유는?
    • 수행 기능은?
    • 사용 방법은?
    ind d; //경과 시간(단위: 날짜)
    
    VS
    
    int elapsedTimeInDays;
    int daysSinceCreation;
    int daysSinceModification;
    int fileAgeInDays;

     

     의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워진다.

    public List<int[]> getThem() {
        List<int[]> list1 = new ArrayList<int[]>();
        for (int [] x : theList) {	// theList에 무엇이 들었는지 알아야 한다.
            if (x[0] == 4) {	// 0번째 값이 왜 중요한지, '4'가 무슨 의미인지 알아야 한다.
                list1.add(x);
            }
        }
        return list1;	// list1은 그냥 int[] 형인데 이걸 받은 쪽에서 어떻게 사용하는지 알아야 한다.
    }
    
    
    VS
    
    
    public List<Cell> getFlaggedCells() {
        List<Cell> flaggedCells = new ArrayList<Cells>();
        for (Cell cell : gameBoard) {
            if (cell.isFlagged()) {
            	flaggedCells.add(cell);
            }
        }
        return flaggedCells;
    }

     


    그릇된 정보를 피하라

    프로그래머는 코드에 그릇된 단서를 남겨서는 안 된다.

    • 예를 들어 hp는 유닉스 플랫폼을 가리키는 이름이므로 직각삼각형의 빗변(hypotenuse)를 구현할 때 hp를 사용하면 독자에게 그릇된 정보를 제공할 수 있다.
    • 배열로 구현해놓고 accountList 라고 명명할 경우 List 자료구조로 보이게 하는 그릇된 정보를 제공하는 셈이다. -> 실제로 List를 썼다고 해도 아예 컨테이너 유형을 이름에 넣지 않는 편이 바람직하다.

     

    서로 흡사한 이름을 사용하지 않도록 주의

    • XYZControllerForEfficientHandlingOfStrings
    • XYZControllerForEfficientStorageOfStrings

     

    유사한 개념은 유사한 표기법을 사용한다.

    • ☆ getName, getAddress, popAge, getPhoneNumber 이런식이면 곤란함

     

    소문자 L이나 대문자 O 변수를 쓰지 말자.

    • O = 1l*O*0l*l;

     


    의미 있게 구분하라

    연속된 숫자를 덧붙이는 방식을 피하자

    • public static void copyChars(char a1[], char a2[])  (X)
    • public static void copyChars(char source, char destination)  (O)

     

    불용어(☆ 분석에 의미가 없는 단어)를 추가한 이름은 아무런정보도 제공하지 못한다.

    • getActiveAccount();
    • getActiveAccounts();
    • getActiveAccountInfo();
    • zork
    • theZork

     

    읽는 사람이 차이를 알도록 이름을 지어라.

     


    발음하기 쉬운 이름을 사용하라

    프로그래밍은 사회 활동이다. 발음하기 쉬운 이름을 선택하자.

    • genymdhms (X)

     


    검색하기 쉬운 이름을 사용하라

    문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다. 또한 검색도 어렵다.

    • MAX_CLASSES_PER_STUDENT (O)
    • 7 (X)

     

    ⚈ e는 영어에서 가장 많이 쓰이는 문자다. 십중팔구 거의 모든 프로그램, 거의 모든 문장에서 등장한다.

    • 이런 관점에서 긴 이름이 짧은 이름보다 좋다.

     


    인코딩을 피하라

    ☆ 정보를 부호화해서 나타내는걸 피하라고 이해하면 될듯함.

     

    자바 프로그래머는 변수 이름에 타입을 인코딩할 필요가 없다. IDE는 코드를 컴파일하지 않고도 타입 오류를감지할 정도로 발전했다. 접두어(헝가리식 표기법이나 기타 인코딩 방식)는 옛날에 작성한 구닥다리 코드라는 징표가 되버린다.

    •  예를들어 int nNumber, int g_nTimer 와 같은 것. (n은 정수임을 표현, g_는 전역변수임을 표현)

     

    인터페이스 클래스 이름 관련

    • [A] 인터페이스 : IShapeFactory, 구현 : ShapeFactory
    • [B] 인터페이스 : ShapeFactory, 구현 : ShapeFactoryImp
    • [A]는 인터페이스라는 사실을 남에게 알린다. 과도한 정보를 제공하므로, 차라리 구현클래스를 인코딩한 [B]가 더 낫다.
    • ☆ 구현 클래스에 Imp도 불용어라 생각함. CircleShapeFactory 처럼 하는게 좋을 것 같다.

     


    자신의 기억력을 자랑하지 마라

    독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못하다.

    • r이라는 변수가 호스트와 프로토콜을 제외한 소문자 URL이라는 사실을 언제나 기억할 수 있을까?
    • 명료함이 최고다.

     


    클래스 이름

    ⚈ 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.

    • Customer, WikiPage, Account, AddressParser

     


    메서드 이름

    메서드 이름은 동사나 동사구가 적합하다.

    • postPayment, deletePage, save
    • 접근자 : get, 변경자 : set, 조건자 : is 로 시작
    • 생성자 중복정의(overload)할 때는 정적 팩토리 메서드를 사용(☆ 정적 팩토리 메서드 : LocaleTime을 리턴해주는 LocaleTime.of() 같은거)

     


    기발한 이름은 피하라

    재미난 이름보다 명료한 이름을 선택하라.

    • HolyHandGenerade (X)
    • DeleteItems (O)
    • ☆ 사실 예시로 나온걸 이해 못해서 왜 저게 재밌는 이름인지 이해 못했음..

     


    한 개념에 한 단어를 사용하라

    이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각한다. 추상적인 개념 하나에 단어 하나를 선택해 이를 고수하자.

    • 예를 들어, 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.

     


    말장난을 하지 마라

    한 단어를 두 가지 목적으로 사용하지 마라.

    • add(); // 더한다
    • add(); // 추가한다. 
    • 이런식으로 쓰지 말란 얘기. 이미 add를 더할 때 쓰고 있었다면, 추가한다는 insert나 append로 하는게 적당함

     


    해법(문제) 영역에서 가져온 이름을 사용하라

    해법 영역에서 먼저 가져오자 - 코드를 읽을 사람도 프로그래머다. 그러므로 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 들을 사용해도 괜찮다.

    • ☆ 예를들어 그래프 형태의 구조에 대해 짜고 있으면서 굳이 간선관계를 association으로 정하는 것 보다는 그냥 edges 정도로 정하면 읽기 훨씬 편함.

     

    적절한 '프로그래머 용어'가 없다면 문제 영역에서 이름을 가져온다.

    • 그럼 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있다.

     


    의미 있는 맥락을 추가하라

    대다수의 이름은 스스로 의미가 분명하지 않다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.

    • firstName, lastName, street, houseNumber, city, state, zipcode, ... -> 주소라는 사실을 알아챌 수 있다.
    • firstName, lastName, state -> 이렇게만 있으면 주소라는 사실을 알아챌 수 없다.
    • 따라서 addrFirstName, addrLastName, addrState -> 이렇게 의미 있는 맥락을 접두어를 추가하더라도 넣어줘야 한다.
    • 더 좋은 방법은 그냥 Address라는 클래스를 생성하는 것이다.

     


    불필요한 맥락을 없애라

    고급 휘발유 충전소(Gas Station Deluxe) 어플리케이션을 짠다고 가정하자. 모든 클래스 이름을 GSD로 시작하겠다는건 바람직하지 못하다.

    • IDE 자동완성에 G만 쳐도 모든 클래스가 다 뜨게될것이다.

     


    마치면서

    여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 누군가 질책할지도 모른다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다.

     

     


    ☆ 실습 해봤다.

      실무에서 작성하는 코드는 그래도 어느정도 생각하며 짜긴 하는데, 알고리즘 문제 풀 때는 사실 절차지향적으로 푸는 편이고, 변수나 함수명도 줄여서 쓰는 경우가 많다. 그나마 좀 객체지향적으로 짠 코드를 기준으로(안그러면 함수나 클래스가 거의 없으니) 책 2장에서 나온 내용을 적용해 리펙토링보기로 했다.

     

    - 원본 코드 (백준 2162를 푼 코드이다.)

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.Arrays;
    import java.util.StringTokenizer;
    
    class Point implements Comparable<Point> {
        int x, y;
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public int compareTo(Point o) {
            if (this.x == o.x)
                return this.y-o.y;
            return this.x-o.x;
        }
    }
    
    class Line {
        Point a, b;
        public Line(Point p1, Point p2) {
            a = p1.compareTo(p2)<=0?p1:p2;
            b = p1.compareTo(p2)<=0?p2:p1;
        }
        public Line(int x1, int y1, int x2, int y2) {
            this(new Point(x1, y1), new Point(x2, y2));
        }
        public boolean isIntersection(Line ano) {
            int res1 = Ccw.getCcw(this.a, this.b, ano.a) * Ccw.getCcw(this.a, this.b, ano.b);
            int res2 = Ccw.getCcw(ano.a, ano.b, this.a) * Ccw.getCcw(ano.a, ano.b, this.b);
            if (res1==0&&res2==0) {
                return this.a.compareTo(ano.b)<=0 && ano.a.compareTo(this.b)<=0;
            }
            return res1<=0 && res2<=0;
        }
    }
    
    class Ccw {
        public static int getCcw(Point a, Point b, Point c) {
            Point[] arr = {a,b,c,a};
            int sum = 0;
            for (int i = 0; i < 3; i++) {
                sum += arr[i].x*arr[i+1].y-arr[i+1].x*arr[i].y;
            }
            return sum>0?1:sum<0?-1:0;
        }
    }
    
    class UnionFind {
        int[] parents;
    
        public UnionFind(int num) {
            parents = new int[num];
            Arrays.fill(parents, -1);
        }
    
        public int find(int a) {
            if (parents[a] < 0) return a;
            return parents[a] = find(parents[a]);
        }
    
        public void union(int a, int b) {
            a = find(a);
            b = find(b);
            if (a == b) return;
            int h = parents[a]<parents[b]?a:b;
            int l = parents[a]<parents[b]?b:a;
            parents[h] += parents[l];
            parents[l] = h;
        }
    }
    
    public class Main {
        UnionFind uf;
        private int ni(StringTokenizer st) { return Integer.parseInt(st.nextToken()); }
        private void solution() throws Exception {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1<<16);
            int n = Integer.parseInt(br.readLine());
            uf = new UnionFind(n);
            Line[] lines = new Line[n];
            StringTokenizer st;
            for (int i = 0; i < n; i++) {
                st = new StringTokenizer(br.readLine());
                lines[i] = new Line(ni(st), ni(st), ni(st), ni(st));
            }
            
            for (int i = 0; i < n; i++) {
                for (int j = i+1; j < n; j++) {
                    if (lines[i].isIntersection(lines[j]))
                        uf.union(i, j);
                }
            }
    
            int cnt = 0;
            int min = 0;
            for (int i = 0; i < n; i++) {
                if (uf.parents[i] < 0) {
                    cnt++;
                    if (uf.parents[i] < min)
                        min = uf.parents[i];
                }
            }
            System.out.printf("%d\n%d\n", cnt, -min);
        }
    
        public static void main(String[] args) throws Exception {
            new Main().solution();
        }
    }

     

     

    - 변경 과정 메모

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.Arrays;
    import java.util.StringTokenizer;
    
    // Ccw에서 상수를 사용하기 위해 추가함
    enum DirectionOfVectors {
        CLOCKWISE(-1),
        COUNTER_CLOCKWISE(1),
        PARALLEL(0);
    
        private int direction;
        DirectionOfVectors(int direction) {
            this.direction = direction;
        }
        int getValue() {
            return this.direction;
        }
    }
    
    // 버퍼 사이즈를 상수로 사용하기 위해 추가함
    enum BufferSize {
        BUFFER_SIZE(1<<16);
    
        private int bufferSize;
        BufferSize(int bufferSize) {
            this.bufferSize = bufferSize;
        }
    
        int getValue() {
            return this.bufferSize;
        }
    }
    
    class Point implements Comparable<Point> {
        int x, y;
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public int compareTo(Point o) {
            if (this.x == o.x)
                return this.y-o.y;
            return this.x-o.x;
        }
    }
    
    class Line {
        //    Point a, b;
    //    a, b로는 의미가 없으므로 변경! 생성 시 정렬해서 들어갈 것인데, 개념상으로 a쪽이 더 작은 크기의 점이 들어갈거라 start로 둠.
        Point startPoint, endPoint;
    
        //    public Line(Point p1, Point p2) {
    //        a = p1.compareTo(p2)<=0?p1:p2;
    //        b = p1.compareTo(p2)<=0?p2:p1;
    //    }
    //    이건 좀 애매하긴 하다. point1, point2가 맞긴함. 임의의 2개의 점을 넣어주는 부분이라. 책에선 쓰지 말랬지만 이게 맞는거같은데.. 어쨌든 p1, p2대신 point1,2로 변경
        public Line(Point point1, Point point2) {
            startPoint = point1.compareTo(point2)<=0?point1:point2;
            endPoint = point1.compareTo(point2)<=0?point2:point1;
        }
    
        //    public Line(int x1, int y1, int x2, int y2) {
    //        this(new Point(x1, y1), new Point(x2, y2));
    //    }
    //    생성자 오버라이드가 필요할 경우 정적 팩토리 메서드를 사용하랬으므로 그렇게 해봄.
        public static Line of(int x1, int y1, int x2, int y2) {
            return new Line(new Point(x1, y1), new Point(x2, y2));
        }
    
        //    public boolean isIntersection(Line ano) {
    //        int res1 = Ccw.getCcw(this.startPoint, this.endPoint, ano.startPoint) * Ccw.getCcw(this.startPoint, this.endPoint, ano.endPoint);
    //        int res2 = Ccw.getCcw(ano.startPoint, ano.endPoint, this.startPoint) * Ccw.getCcw(ano.startPoint, ano.endPoint, this.endPoint);
    //        if (res1==0&&res2==0) {
    //            return this.startPoint.compareTo(ano.endPoint)<=0 && ano.startPoint.compareTo(this.endPoint)<=0;
    //        }
    //        return res1<=0 && res2<=0;
    //    }
    //    줄여서 써 놓은 부분들 풀로 작성. result1, 2의 경우 중간 계산식이라 둘이 합쳐져야 의미가 있어서 어떻게 처리해야할지 모르겠다.
    //    아래처럼 나눠서 표현해보려고 했는데, 이러면 또 '그릇된 정보를 피하라 - 서로 흡사한 이름을 사용하지 않도록 주의' 부분에 딱 걸린다.
    //    이 부분은 좀 생각을 해봐야 할 것 같다.
    //    
    //    private int intermediateDirectionFormula(Line baseLine, Line anotherLine) {
    //        int directionToAnotherLineStartPoint = Ccw.directionOfVectors(baseLine.startPoint, baseLine.endPoint, anotherLine.startPoint);
    //        int directionToAnotherLineEndPoint = Ccw.directionOfVectors(baseLine.startPoint, baseLine.endPoint, anotherLine.endPoint);
    //        return directionToAnotherLineStartPoint * directionToAnotherLineEndPoint;
    //    }
    //
    //    public boolean isIntersection(Line anotherLine) {
    //        int intermediateDirectionFormulaOfBase = intermediateDirectionFormula(this, anotherLine);
    //        int intermediateDirectionFormulaOfAnother = intermediateDirectionFormula(anotherLine, this);
    //        if (intermediateDirectionFormulaOfBase==0 && intermediateDirectionFormulaOfAnother==0) {
    //            return this.startPoint.compareTo(anotherLine.endPoint)<=0 && anotherLine.startPoint.compareTo(this.endPoint)<=0;
    //        }
    //        return intermediateDirectionFormulaOfBase<=0 && intermediateDirectionFormulaOfAnother<=0;
    //    }
        public boolean isIntersection(Line anotherLine) {
            int result1 = Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.startPoint) * Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.endPoint);
            int result2 = Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.startPoint) * Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.endPoint);
            if (result1==0&&result2==0) {
                return this.startPoint.compareTo(anotherLine.endPoint)<=0 && anotherLine.startPoint.compareTo(this.endPoint)<=0;
            }
            return result1<=0 && result2<=0;
        }
    }
    
    class Ccw {
        //    public static int getCcw(Point a, Point b, Point c) {
    //        Point[] arr = {a,b,c,a};
    //        int sum = 0;
    //        for (int i = 0; i < 3; i++) {
    //            sum += arr[i].x*arr[i+1].y-arr[i+1].x*arr[i].y;
    //        }
    //        return sum>0?1:sum<0?-1:0;
    //    }
    //    ccw가 해법의 영역에서 가져온 이름이긴 하지만, ccw 자체의 사용법이 다양하고 여기선 단순히 방향성만 확인하므로 방향성을 더 잘 나타내도록 변경
    //    또한 벡터 a->b와 a->c의 방향성을 나타내므로 그에 맞게 a,b,c도 변경. arr은 shoelace formula 사용을 위한 것이므로 변경
    //    방향성을 숫자가 아니라 상수로 빼서 작성. 다만 계산식에 쓰일거라서 사용하는쪽에서 어차피 상수로 안쓸꺼라 이 부분은 좀 애매한 것 같다.
        public static int directionOfVectors(Point basePoint, Point firstEndPoint, Point secondEndPoint) {
            Point[] arrayForShoelaceFormula = {basePoint,firstEndPoint,secondEndPoint,basePoint};
            int sum = 0;
            for (int i = 0; i < 3; i++) {
                sum += arrayForShoelaceFormula[i].x * arrayForShoelaceFormula[i+1].y - arrayForShoelaceFormula[i+1].x * arrayForShoelaceFormula[i].y;
            }
            if (sum > 0)
                return DirectionOfVectors.COUNTER_CLOCKWISE.getValue();
            else if (sum < 0)
                return DirectionOfVectors.CLOCKWISE.getValue();
            return DirectionOfVectors.PARALLEL.getValue();
        }
    }
    
    class UnionFind {
        int[] parents;  // 해법의 영역에 해당하는 용어이므로 그대로 둬도 될 듯 하다.
    
        public UnionFind(int num) {
            parents = new int[num];
            Arrays.fill(parents, -1);
        }
    
        //    public int find(int a) {
    //        if (parents[a] < 0) return a;
    //        return parents[a] = find(parents[a]);
    //    }
    //    a 대신 인덱스로 변경. find는 해법의 영역에 해당하므로 그대로 둔다.
        public int find(int index) {
            if (parents[index] < 0) return index;
            return parents[index] = find(parents[index]);
        }
    
        //    public void union(int a, int b) {
    //        a = find(a);
    //        b = find(b);
    //        if (a == b) return;
    //        int h = parents[a]<parents[b]?a:b;
    //        int l = parents[a]<parents[b]?b:a;
    //        parents[h] += parents[l];
    //        parents[l] = h;
    //    }
    //    a, b에 어떤게 들어와도 되고 그냥 2개만 들어오면 되는 형태라 이것도 좀 애매하다. 둘의 차이가 없으므로 숫자를 붙이는게 맞을 것 같다.
    //    현재 내가 구현한 분리 집합 알고리즘이 랭크를 이용한 구현이라 h와 l은 높은 랭크와 낮은 랭크를 의미한다. 이것도 변경했다.
    //    union은 해법의 영역에 해당하는 용어이므로 그대로 둔다.
        public void union(int index1, int index2) {
            index1 = find(index1);
            index2 = find(index2);
            if (index1 == index2) return;
            int highRankedIndex = parents[index1]<parents[index2]?index1:index2;
            int lowRankedIndex = parents[index1]<parents[index2]?index2:index1;
            parents[highRankedIndex] += parents[lowRankedIndex];
            parents[lowRankedIndex] = highRankedIndex;
        }
    }
    
    public class Main {
        //    UnionFind uf;
    //    그냥 풀 네임으로 변경
        UnionFind unionFind;
    
        //    private int ni(StringTokenizer st) { return Integer.parseInt(st.nextToken()); }
    //    풀 네임으로 변경
        private int nextInt(StringTokenizer stringTokenizer) { return Integer.parseInt(stringTokenizer.nextToken()); }
    
        private void solution() throws Exception {
    //        BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1<<16);
    //        풀네임으로 변경 및 버퍼 사이즈 지정한 부분(메모리 초과를 피하기 위해)을 상수로 변경
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in), BufferSize.BUFFER_SIZE.getValue());
    
            int n = Integer.parseInt(bufferedReader.readLine());
            unionFind = new UnionFind(n);
            Line[] lines = new Line[n];
    //        StringTokenizer st;
    //        풀네임으로 변경
            StringTokenizer stringTokenizer;
            for (int i = 0; i < n; i++) {
                stringTokenizer = new StringTokenizer(bufferedReader.readLine());
    
    //            lines[i] = new Line(ni(st), ni(st), ni(st), ni(st));
    //            변경된 정적 팩토리 메소드를 사용하도록 변경
                lines[i] = Line.of(nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer));
            }
    
            for (int i = 0; i < n; i++) {
                for (int j = i+1; j < n; j++) {
                    if (lines[i].isIntersection(lines[j]))
                        unionFind.union(i, j);
                }
            }
    
            int cnt = 0;
            int min = 0;
            for (int i = 0; i < n; i++) {
                if (unionFind.parents[i] < 0) {
                    cnt++;
                    if (unionFind.parents[i] < min)
                        min = unionFind.parents[i];
                }
            }
            System.out.printf("%d\n%d\n", cnt, -min);
        }
    
        public static void main(String[] args) throws Exception {
            new Main().solution();
        }
    
    }

     

     

    - 변경 결과

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.Arrays;
    import java.util.StringTokenizer;
    
    enum DirectionOfVectors {
        CLOCKWISE(-1),
        COUNTER_CLOCKWISE(1),
        PARALLEL(0);
    
        private int direction;
        DirectionOfVectors(int direction) {
            this.direction = direction;
        }
        int getValue() {
            return this.direction;
        }
    }
    
    enum BufferSize {
        BUFFER_SIZE(1<<16);
    
        private int bufferSize;
        BufferSize(int bufferSize) {
            this.bufferSize = bufferSize;
        }
    
        int getValue() {
            return this.bufferSize;
        }
    }
    
    class Point implements Comparable<Point> {
        int x, y;
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public int compareTo(Point o) {
            if (this.x == o.x)
                return this.y-o.y;
            return this.x-o.x;
        }
    }
    
    class Line {
        Point startPoint, endPoint;
    
        public Line(Point point1, Point point2) {
            startPoint = point1.compareTo(point2)<=0?point1:point2;
            endPoint = point1.compareTo(point2)<=0?point2:point1;
        }
    
        public static Line of(int x1, int y1, int x2, int y2) {
            return new Line(new Point(x1, y1), new Point(x2, y2));
        }
    
        public boolean isIntersection(Line anotherLine) {
            int result1 = Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.startPoint) * Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.endPoint);
            int result2 = Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.startPoint) * Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.endPoint);
            if (result1==0&&result2==0) {
                return this.startPoint.compareTo(anotherLine.endPoint)<=0 && anotherLine.startPoint.compareTo(this.endPoint)<=0;
            }
            return result1<=0 && result2<=0;
        }
    }
    
    class Ccw {
        public static int directionOfVectors(Point basePoint, Point firstEndPoint, Point secondEndPoint) {
            Point[] arrayForShoelaceFormula = {basePoint,firstEndPoint,secondEndPoint,basePoint};
            int sum = 0;
            for (int i = 0; i < 3; i++) {
                sum += arrayForShoelaceFormula[i].x * arrayForShoelaceFormula[i+1].y - arrayForShoelaceFormula[i+1].x * arrayForShoelaceFormula[i].y;
            }
            if (sum > 0)
                return DirectionOfVectors.COUNTER_CLOCKWISE.getValue();
            else if (sum < 0)
                return DirectionOfVectors.CLOCKWISE.getValue();
            return DirectionOfVectors.PARALLEL.getValue();
        }
    }
    
    class UnionFind {
        int[] parents;
    
        public UnionFind(int num) {
            parents = new int[num];
            Arrays.fill(parents, -1);
        }
    
        public int find(int index) {
            if (parents[index] < 0) return index;
            return parents[index] = find(parents[index]);
        }
    
        public void union(int index1, int index2) {
            index1 = find(index1);
            index2 = find(index2);
            if (index1 == index2) return;
            int highRankedIndex = parents[index1]<parents[index2]?index1:index2;
            int lowRankedIndex = parents[index1]<parents[index2]?index2:index1;
            parents[highRankedIndex] += parents[lowRankedIndex];
            parents[lowRankedIndex] = highRankedIndex;
        }
    }
    
    public class Main {
        UnionFind unionFind;
    
        private int nextInt(StringTokenizer stringTokenizer) { return Integer.parseInt(stringTokenizer.nextToken()); }
    
        private void solution() throws Exception {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in), BufferSize.BUFFER_SIZE.getValue());
            int n = Integer.parseInt(bufferedReader.readLine());
            unionFind = new UnionFind(n);
            Line[] lines = new Line[n];
            StringTokenizer stringTokenizer;
            for (int i = 0; i < n; i++) {
                stringTokenizer = new StringTokenizer(bufferedReader.readLine());
                lines[i] = Line.of(nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer));
            }
    
            for (int i = 0; i < n; i++) {
                for (int j = i+1; j < n; j++) {
                    if (lines[i].isIntersection(lines[j]))
                        unionFind.union(i, j);
                }
            }
    
            int cnt = 0;
            int min = 0;
            for (int i = 0; i < n; i++) {
                if (unionFind.parents[i] < 0) {
                    cnt++;
                    if (unionFind.parents[i] < min)
                        min = unionFind.parents[i];
                }
            }
            System.out.printf("%d\n%d\n", cnt, -min);
        }
    
        public static void main(String[] args) throws Exception {
            new Main().solution();
        }
    }

     

      일단 변경된 코드도 정답으로 체크되는걸보니 잘못 변경한 부분은 없는 것 같다.

     

      솔직히 수식 부분은 더 길어지면서 더 알아먹기 힘들어진 것 같긴하다. 그리고 1,2 부분을 어떻게 처리할지 감이 안온다. 실무 영역에서는 그래도 좀 떨어지는 부분이 많은데 알고리즘은 아무래도 절차지향이 더 어울리다보니 적용하기 힘든 것 같다. 아무래도 실습 예제를 잘 못 고른것 같닼ㅋㅋㅋ. 그래도 한번씩 이렇게 의식적으로 해보면 자연스레 다른 곳에도 적용될테니 도움이 될 것 같다.

     

    'Study > 클린코드' 카테고리의 다른 글

    [클린코드] 6장. 객체와 자료 구조  (0) 2023.01.02
    [클린코드] 5장. 형식 맞추기  (0) 2023.01.02
    [클린코드] 4장. 주석  (0) 2022.12.21
    [클린코드] 3장. 함수  (0) 2022.12.20
    [클린코드] 1장. 깨끗한 코드  (0) 2022.12.14

    댓글