- 함수 내부의 조건 분기는 호출자 쪽으로 올리고, 반복 대상 작업은 개별 호출보다 배치 연산으로 내리면 코드 흐름과 성능을 함께 단순화할 수 있음
if를 위로 올리면 전제조건을 타입이나 assert로 보장하기 쉬워지고, 함수 안의 중복 검사와 불필요한 분기가 줄어듦- 분기 로직이 여러 함수에 흩어지면 죽은 조건이나 반복된 판단을 찾기 어려우며, enum을 만들고 다시 match하는 구조도 같은 분기를 데이터 구조로 되풀이하는 형태가 될 수 있음
for를 아래로 내리면 여러 객체를 한 번에 처리해 시작 비용을 상각하고, 처리 순서 재배치나 벡터화 같은 최적화 여지가 생김- 두 규칙을 함께 쓰면 반복문 내부 조건 재평가와 핫 루프의 분기가 줄어들어, 제어 평면의 의사결정 비용을 데이터 평면의 배치 처리로 넘기는 구조에 가까워짐
if는 호출자 쪽으로 올리기
- 함수 내부에
if조건이 있다면 그 조건을 호출자로 옮길 수 있는지 먼저 검토할 수 있음Option<Walrus>를 받아None이면 반환하는 함수보다, 이미Walrus를 받는 함수가 더 단순한 형태임- 함수가 전제조건을 확인하고 “아무것도 하지 않는” 대신, 호출자가 확인한 뒤 타입이나 assert로 보장할 수 있음
- 전제조건 검사를 위로 올리는 방식은 호출 경로 전체로 퍼질 수 있으며, 결과적으로 검사 횟수를 줄이는 동기가 됨
- 제어 흐름과
if는 코드 복잡도와 버그 가능성을 키우기 쉬움- 복잡한 분기 로직은 상위 함수 한곳에 모이고, 실제 작업은 직선적인 하위 함수로 위임되는 구조가 읽기 쉬움
- 복잡한 제어 흐름이 파일 전체에 흩어지는 것보다 한 화면 안의 단일 함수에 모여 있을 때 중복 조건과 죽은 조건을 알아차리기 쉬움
- “dissolving enum” 리팩터링은 enum 생성과 match가 같은 분기를 반복할 때 유용함
f()가 조건에 따라E::Foo또는E::Bar를 만들고,g()가 다시 match해foo()또는bar()를 호출하면 분기가 두 번 나타남- 조건을 위로 끌어올리면
main()에서 같은 조건으로 곧바로foo(x)또는bar(y)를 호출하는 형태가 됨
for는 배치 연산 쪽으로 내리기
- 데이터 지향 사고에서는 프로그램이 보통 여러 객체 묶음을 다루고, 핫 패스도 많은 엔티티를 처리하기 때문에 뜨거워지는 경우가 많음
- 객체의 배치 개념을 도입하고, 스칼라 버전은 배치 버전의 특수 사례로 두는 방식이 유용함
for walrus in walruses { frobnicate(walrus) }보다frobnicate_batch(walruses)가 더 나은 형태임
- 배치 처리의 주된 이점은 성능임
- 여러 대상을 한 번에 다루면 시작 비용을 상각할 수 있음
- 처리 순서를 유연하게 바꿀 수 있고, 특정 순서로 엔티티를 처리할 필요도 줄어듦
- 모든 엔티티의 한 필드를 먼저 처리한 뒤 다른 필드로 넘어가는 벡터화나 struct-of-array 기법을 쓸 수 있음
- 극단적인 성능 사례로 Vectorized Interpreters Talk가 연결됨
- 재미있는 예로 FFT 기반 다항식 곱셈이 있음
- 여러 점에서 다항식을 동시에 평가하는 방식이 개별 점 평가를 여러 번 하는 것보다 빠를 수 있음
if와for규칙은 함께 적용할 수 있음- 조건이 반복문 밖에 있으면
condition을 반복해서 다시 평가하지 않음 - 핫 루프에서 분기가 제거됨
- 벡터화 가능성이 열림
- 조건이 반복문 밖에 있으면
- 같은 패턴은 미시적 수준과 거시적 수준 모두에서 작동함
- TigerBeetle의 구조는 데이터 평면에서 객체 배치를 동시에 처리해, 제어 평면의 의사결정 비용을 상각하는 방식임
for규칙의 주된 동기는 성능이지만, 표현력에도 도움이 될 수 있음jQuery는 요소 컬렉션을 대상으로 동작했고 과거에 성공적이었음- 추상 벡터 공간의 언어는 좌표별 방정식 묶음보다 사고 도구로 더 나을 때가 있음