🧐 TIL (Today I Learned)

1. 미션

forEach

// 코드 1

    public String listPawnsToSingleLine(ArrayList<Pawn> whichGroupOfPawns) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < BOARD_LENGTH; i++) {
            sb.append(whichGroupOfPawns.get(i).getRepresentation());
        }

        return sb.toString();
    }

처음엔 코드 1을 forEach 문으로 바꿀 수 있다는 것을 생각을 못했다. 왜냐하면 일단 forEach 가 가독성이 좋다지만 내게는 아직 for문이 더 쓰기 편하고 언제 쓸 수 있는지 정확히 파악하지 못했기 때문이다.

오늘 이 두 가지만 충족시키면 forEach문을 쓸 수있다는 것을 배웠다.

  1. 이미 배열이 있는 상태이다.
  2. 그 배열을 변경할 수는 없다.

코드 1을 살펴보자면 파라미터로 whichGrupOfPawns 를 받는데 이 ArrayList는 이미 배열이 정해져있는 상태이므로 1을 충족시킨다.

2는 StringBuilder에 append를 한다는 거에 약간 헷갈려서 조건을 충족시킬 수 없다고 잠깐 생각했다. 근데 코드 1에서는 전혀 forEach와 StringBuilder는 전혀 관계가 없다. 단순히 이미 있는 배열의 각 인덱스를 참조해서 StringBuilder에 append 하는거기 때문이다.

코드 1을 forEach 로 바꾸면 코드 2처럼 된다.

// 코드 2

    public String listPawnsToSingleLine(ArrayList<Pawn> whichGroupOfPawns) {
        StringBuilder sb = new StringBuilder();

        for (Pawn p : whichGroupOfPawns)
            sb.append(p.getRepresentation());

        return sb.toString();
    }

2. List list = new ArrayList(); 아니면..?

항상 ArrayList 쓸 때 고민인 것은 List list = new ArrayList(); 이냐 아니면 ArrayList list = new ArrayList(); 이냐이다.

List로 받으면 쉽게 다른 List 인터페이스의 다른 구현체로 변경이 가능하다. 그래서 큰 프로젝트나 자신의 API를 만들때 List를 쓰면 코드를 다 작성하고나서 필요에따라 쉽게 필요한 List로 대체할 수 있다. 큰 프로젝트에서는 그 역할이 더 막강해진다.

Polymorphism: Why use “List list = new ArrayList” instead of “ArrayList list = new ArrayList”?

3. 생성자에 int를 넣어주는 ArrayList(int i)

ArrayList를 생성자 없이 그냥 생성하면 capacity 가 10이다. 그런데 i를 직접 지정해주면 initial capacity 를 지정할 수 있다.

그런데 여기서 size 와 capacity 는 무엇이 다른걸까? size는 흔히 사용해왔듯이 add한 데이터의 길이를 나타낸다. capacity는 그 단어의 어감이 최대로 넣을 수 있는 데이터의 upper limit 이라고 생각할 수 있는데, 단지 얼마나 많은 데이터를 현재 ArrayList에 담을 수있는 지를 나타내는 값일 뿐이다.

ArrayList를 배울 때 배열처럼 길이를 지정해주는 게 아니라 데이터를 넣을 때 유동적으로 바뀐다는 것을 배웠다. 지금 생각해보면 분명 ArrayList 를 선언할 때 기본적으로 용량이 정해져있을 것이다. 근데 그게 999999999는 아닐 것이다. 메모리 남용이니까. 그런 개념으로 capacity가 있는 것이다. 처음에 일정량 들을어 올수 있는 값을 정해놓고 꽉 채워지면 내부적으로 정해진 룰에 따라 (1.5배 + 1) 용량을 계속 늘려나가는 것이다.

API 에서 ArrayList를 제공하는 인터페이스(List)는 기본적으로 capacity 를 사용하는 것과 같은 기능을 제공하지 않는다. 주요한 이유 중 하나는 사용자가 이것을 쓸때없이 신경쓰지 않도록 하기 위함이다. 왜냐면 capacity 같은 추상적인 개념에서 벗어나 ArrayList는 무제한 배열로 생각되어져야 하기 때문이다.

Capacity of ArrayList

리뷰어의 추가 내용:
ArrayList 가 Array List 인 이유가 있겠죠.
내부적으로 배열을 사용합니다. 말씀하신 것처럼 처음부터 너무 큰 배열을 선언하면 메모리 낭비가 되니까, 필요한 최소한을 만들어두고 계속된 .add() 로 용량이 증대될 필요가 있을 경우 그때 비로소 더 넓은 배열을 생성하고, 이사가는 것입니다.
이때 더 넓은 배열을 생성하고 이사가는 작업 은 컴퓨터의 연산을 다소 필요로 합니다. 자동으로 관리되도록 내버려둬도 괜찮을텐데, 굳이 ArrayList 클래스의 생성자로 capacity를 지정할 수 있게 한 것은, 개발자의 책임과 판단에 따라 큰 용량이 처음부터 필요할 리스트 객체에 대해선 미리 넓은 공간을 확보할 수 있게 하여, 연산력 소모를 아낄 수 있는 개발 방법을 제공하기 위해 그렇게 한 것입니다.
출처: https://github.com/codesquad-members-2021/java-chess/pull/75#discussion_r577215423

4. git branch, merge, conflict

오늘 git branch 부터 시작해서 pr 까지 종합적으로 엄청 여기저기 돌아다니며 많은 공부를 했다. 내가 정확히 이해하지 못했던 가장 핵심은 conflict 였다. conflict는 같은 곳을 수정하면 발생하는 것을 말한다. 기본적으로 merge하면 코드들을 자동적으로 더하고 수정하는 것을 git merge 명령어가 알아서해주는데 같은 곳을 수정하면 그렇게 바꿔주지 않는다. 여기서 2 way merge 와 3 way merge 를 알아야한다.

2 way merge

2개의 브랜치를 비교해서 merge 하는 것을 말한다. 단순히 코드끼리 비교해서 코드가 추가됐거나 빠진 건 알아서 더해서 커밋을 만들지만, 같은 곳을 수정하게 되면 conflict가 발생한다. 라인 단위로 같은 곳이 두 곳이면 두 곳이 충돌이 일어나고 세 곳이면 세 곳에서 발생한다. 이렇게 두 브랜치끼리 비교하는 것을 2 way merge라고 한다.

3 way merge

중요하다. 3 way merge는 2 way merge를 좀 더 잘 대처하기 위해서 나왔다. 3 way merge는 merge하려는 두 커밋과 공통된 조상 (베이스) 를 비교해서 수정된 부분으로 merge한다. 이걸 쉽게해주는 tool이 있다.

git mergetool 라고 입력하면 GUI로 된 mergetool을 사용할 수 있는데, 이건 인터넷에서 다운받아서 gitconfig 도 설정해줘야한다. 많은 mergetool 이 있는데 그중에서 생활코딩 같은 경우 p4merge 를 안내한다.

설정까지 모두 마치면 깃이 충돌난 상황에서 git mergetool 이라고 입력하면 아까 설정한 p4merge가 실행된다. mergetool은 커밋사이의 공통조상, 즉 베이스와 비교해서 자동으로 merge한 결과를 아래에 보여준다.

자동으로 해주는 부분은 베이스의 커밋을 기준으로 베이스와 코드가 다르면 코드가 다른 커밋의 내용으로 대체한다. 그러나 베이스 커밋을 기준으로 merge하려는 두 커밋 모두 같은 부분을 서로 달리 코드를 수정했다면 mergetool에서 직접 원하는 코드로 수정해서 저장해주면 된다.

mergetool 사용하기

그외

  • 커밋간의 공통조상을 base라고 한다.

✅To-do

미션 3 피드백, 리뷰 오면 수정, 리뷰올떄까지 다른분들 PR보기 ㄱㄱ

미션 4 PR 하기

나중에 해야 할 To-do 링크


📚일기

저번에 PR 중에 충돌난 거 해결하는 걸 정확하게 알기위해서 고군분투했다. conflict 개념 자체에 대해서 공부해보라고 sony가 미션2에서 리뷰해줬던거 같다. 근데 그렇게 중요하게 안들었다. conflict에서 오는 어감으로 모든 걸 이해했다고 생각한 모양이다.

실제 상황을 재현해서 계속 나름 이해한대로 fetch, rebase, merge를 반복하며 계쏙 시도해봤는데 완전 무리였다. 계속 충돌나고 뭐가 원인인지 알 길이 없었다. 당연하다.

내가 원하는 건 충돌 일어났을 때 파일을 수정하는 게 아니었다. 어떻게든지 merge나 rebase를 통해서 해결이 가능한건 줄 알았다. 근데 충돌 자체에 대한 개념을 제대로 배우면서 완전 어긋나게 생각하고 있었다는 걸 깨달았다.

그래서인지 아침 스크럼 때 정확한 이해없이 장황한 질문을 했었는데 그것 때문에 팀원들이 고생한 것 같아 괜스레 죄송했다. 코드스쿼드 하면서 질문 자체에 대한 고민을 엄청 많이하는 것 같다. 내가 살면서 이렇게 질문 자체에 대해 고민한 적이 있었나? 코드스쿼드에서 공부하면서 같이 공부한다는 것에 대해서도 많이 배운다.

profile
제 생각이 담긴 블로그입니다

0개의 댓글