티스토리 뷰
코딩을 하다 보면 무아지경으로 코드를 써 내려가고 있는 경우가 있다.
그 동안에는 희안하게도 머리속에서 로직이 자동으로 정리되어 가면서 술술 써내려가게 된다.
그러나, 이렇게 작성을 했다면 반드시 후폭풍을 주의해야 한다.
어떤 한 가지 일을 하게 되면서 '습관'이 안 생길 수가 없다. 생기지 않는 것이 오히려 더 이상하다.
그런데, 이 '습관' 중에서 완벽한 습관이라고 할 수 있는 습관도 없다.
그렇기에 프로젝트에서 복기가 중요한 것이고, 동시에 리팩토링을 진행하는 이유이기도 한 것이다.
그렇다면 한번 살펴보자. 실수하기 쉬운 부분들을 말이다.
아, 참고로 GetComponent도 있는데, 이건 이전에 정리했으니까 이번에는 생략하겠다.
foreach문의 함정
C#에서 쓸 수 있는 반복문은 크게는 2가지, 세부적으로는 4가지가 있다.
- for문
- for(i = 0; i < max; i++) 문
- foreach(var value in valueArray) 문
- while문
- while() {} 문
- do{} while() 문
for문과 while문은 서로 용도가 다른 편이다. while문 안에서도 while~문과 do~while문은 서로 용도가 다른 편.
그러나 for문과 foreach문은 용도가 거의 똑같다.
그렇기 때문에 '그 영향도 같다'고 생각하기가 쉽고, 그렇다 보니 둘 중 하나로 습관이 잡히기도 쉽다.
거기다가 자료형이 명시되기도 하고, 코드 길이도 줄이기 좋아서 얼씨구나 하고 쓰기가 딱 좋능데, 사실 엄밀히 따지자면 일반적으로 쓰이는 반복문으로 foreach를 쓰는 것은 바람직하지 못하다.
왜 그런지 for문과 foreach문의 비교를 통해서 알아보자.
for문
- for문 안쪽의 정수 연산에 의해서 제어된다.
- 배열이나 리스트의 경우 몇 번째 인덱스인지 내부에서 명시해 줘야 한다.
[= 자동으로 인덱스가 넘어가지 않는다.]
foreach문
- 배열이나 리스트의 인덱스를 통해서 제어된다.
[= 자동으로 인덱스가 넘어간다] - '반드시' 배열이나 리스트의 인덱스를 따라야 한다.
그리고, 여기에서 foreach문에는 가장 중요한 특성을 하나 더해야 한다.
- 해당 index의 내용을 지정하는 변수를 반복 마다 생성해서 루프를 돈다.
이것이 int 같은 기본 자료형이면 크게 문제되지는 않는 사항이다.
[물론, 이 경우에도 추가 연산이 생기니까 막 쓰는 것은 좋지 않다.]
하지만, 이것이 클래스 혹은 구조체라면 어떨까.
클래스는 힙 영역에 생성되며 자료 단위가 특정이 되어있지 않은 자료형. 다시 말하면, 가비지 컬렉터가 비상 난무를 출 것이라는 것이 예상되는 사항이다.
구조체는 스택에서 생성되는 것 만 다를 뿐, 자료 단위가 특정이 되어있지 않은 건 동일하다. 즉, 이쪽도 메모리 오버플로우 가능성이 있다는 문제가 발생한다.
때문에 foreach문은 일반적인 상황이 아니라, '변수 선언과 동시에 무언가를 해야하는 상황'에 쓰는 것이 좋다.
아니면 씬 시작 시에 1번 만 호출되는 경우에 만 쓰는 것이 좋다.
묶어서 쓰면 묶어서 써진다.
간혹가다가 '통합 모듈' 목적으로 기능들을 묶어서 작성하는 경우가 있다.
하지만, 이렇게 작성하면 쓰이지도 않는 기능들이 우후죽순 실행되고 있는 것과 마찬가지라서 최적화 상으로 문제가 아주 많아진다.
더군다나 이렇게 짜면 나중에 리팩토링 할 때에도 살~짝 난감해진다. 특히 외부 자료와의 연동과 관련해서 이미 작업을 해 놨다면 리팩토링이 매우 복잡해진다.
이렇게 작성했다면 작성하자 마자 바로 리팩토링을 진행하도록 하자. 가능한 한 빨리 진행해야지 개발 프로세스 간 혼선이 발생하지 않는다.
[만약 눈치채지 못하고 다른 작업 까지 넘어갔다면, 관련 사항을 모두 수정하지 않는 방향으로 리팩토링을 시행해야 한다...]
가장 좋은 방법은 통합적으로 구현한 각 요소를 모두 모듈로 빼 놓은 뒤에, 그에 대한 트리거 역할을 하는 통합 관리자를 배치하는 것.
이렇게 구성한 뒤에 필요한 기능들 만 모듈로 끼워넣는 방식으로 구성하는 것이 가장 적합하다.
이러한 구조를 만들기 위해서 사용되는 것이 디자인 패턴이고, 궁극적으로 이런 식으로 작성을 해야지 유지보수가 편하다.
상호 반환구조
마찬가지로 생각나는 대로 작성하다 보면 은근히 흔하게 발생하는 문제다.
간단하게 설명해 보자.
A클래스에서 a라는 값을 B 클래스에게 넘겨준다고 하자, 그리고 A클래스의 다른 라인에서 a값을 또 사용한다고 하자.
생각없이 짜다 보면 a값을 A 내부에서 저장했다가 쓰는 게 아니라 B에서 반환받고 있는 경우가 생긴다.
영락없이 의미 없는 상호 의존관계가 발생하는 셈이다.
그래도 이건 보기가 쉬운 편이다.
순회 참조, 순회 호출
간단하게 말해보자.
A클래스에서 a 함수로 B 클래스의 b 함수를 불러내고 B 클래스의 b 함수로 C 클래스의 c 함수를 부르고 C 클래스의 c 함수로 D 클래스의 d 함수를 호출하여 처리하는 기능이 있다고 가정해 보자.
작성한 본인 말고 이 코드를 읽을 수 있는 사람이 있을까?
마찬가지로, A클래스의 a 함수가 B 클래스의 b 값을 필요로 하는데, 이 값은 C 클래스의 c 함수를 사용해서 도출한다. 그리고 그 결과를 D 클래스의 d 값으로 사용한다고 해보자.
말로만 해도 복잡한데, 수 일 ~ 수 주 뒤의 본인이 이걸 보고 기능을 알 수 있을까?
이건 주석으로 작성한다고 해도 문제가 큰 편이며, 흔히 말하는 스파게티 코드의 그 어감에 가장 잘 들어맞는 케이스다.
그리고 발견한다고 해도 당장에 눈앞이 캄캄해지기 딱 좋은 구성이기도 하다.
일단 모든 상황에 통용 가능한 해결방법은 아니겠지만, 일단 대체적으로 사용할 수 있는 해결법은 있다.
저걸 다 모듈로 처리한 다음에 E클래스에서 통합 관리하자.
그러니까, 순회 구조로 만드는 게 아니라 E클래스라는 '중재자'를 만들어서 관리하는 것이다.
이렇게 하면 저 순회 구조가 모두 눈에 들어오게 되고, 동시에 수정하기도 쉽게 된다.
물론, 저게 다른 오브젝트 간의 상호작용이면 저 방법을 쓰기가 난처해지긴 한다.
이런 경우에 쓰는 게 매니저이긴 한데... 이걸로 처리하기도 난감한 경우도 있다.
상황에 맞게 고심해서 대처하도록 하자.
자료 구조는 주의해서 다루자.
간혹가다가 편하게 다루겠답시고 한 오브젝트 내부에서 같은 인터페이스나 부모 클래스로 묶이는 클래스들을 GetComponentInChildren<T>()으로 묶어서 가져온 뒤에 배열로 제어하는 경우가 있다.
편하긴 하다. 편하긴 한데, 이렇게 제어하려고 한다면 일단 코드를 한번 더 천천히 살펴보도록 하자.
제어하는 클래스도 같은 인터페이스를 상속받고 있고, 그 인터페이스 효과가 같은 이름의 함수 동시 호출이라면 무한 루프가 발생한다.
이런 경우 이외에도 이렇게 '비특정적인 수단'으로 제어하려고 할 경우에는 문제가 발생하기가 매우 쉬워서 가능하면 지양해야 한다.
당연하지만 '비특정적인 수단'과 '추상화'는 구분을 해야한다.
추상화는 해당 클래스에서는 특정을 짓지 않는 대신 다른 클래스 내지는 알고리즘 상에서 특정해서 사용하는 경우고, 상술한 상황은 특정할 요소가 단 하나도 없는 상황에서 사용하는 것이다.
개발 과정에서 조금이라도 잘못하면 문제가 발생하는 방식이라서, 이러한 '비특정적인 수단'은 지양하는 것이 좋다.
코딩에 익숙해질 대로 익숙해진 사람이라면 금방 눈치채겠지만, 본문의 상황은 상술한 '무아지경의 극치'인 상황에서만 발생하는 문제가 아니다. 의외로 신중하게 고려하고 작성해도 곧잘 생기는 문제점이다.
리팩토링이 괜히 필요한 게 아니라는 것을 가장 잘 느낄 수 있는 부분이기도 하며, 동료가 필요한 가장 큰 이유이기도 하다.
잊지말자. 본인이 작성한 코드의 하자는 본인은 거의 못본다.
피드백을 줄 수 있는 동료나 멘토가 있는 환경이 가지는 가장 큰 이점이기도 하다.
그럴 수 있는 동료가 한 명도 없는 상황이라면... 작성한 코드를 한숨 푹~ 잔 뒤에 다시 한번 읽어 보자.
그래도 못보겠지만 그래도 좀 보이기는 할 것이다.
'스파르타 내일배움캠프 > Today I Learned' 카테고리의 다른 글
Today I Learned - Day 66 [인스턴스는 과유불급] (1) | 2024.12.16 |
---|---|
Today I Learned - Day 65 [스크립트에서 UI 표시 순서 정리하기] (1) | 2024.12.13 |
Today I Learnd - Day 63 [리플렉션] (0) | 2024.12.11 |
Today I Learned - Day 62 [클래스 생성을 string으로 하기] (0) | 2024.12.10 |
Today I Learned - Day 61 [디자인 패턴 - 2] (1) | 2024.12.09 |