효율적인 프로그래밍이나 코딩을 위해서 알아야 할 것을 모아서 정리 해 보았습니다.
막연하게 효율적인 알고리즘? 이나 자료구조를 사용해야 된다는 정도만 알지
사실 제대로 알 지 못하는 점이 많았습니다.
이번 포스팅을 기회로 개인적으로도 공부할 수 있는 기회가 되었습니다.
주의 : 포스팅은 지극히 초보의 관점에서 진행합니다.
부족한게 많아서 사실 저도 잘 정리하면서도 모르겠네요.
계속 학습하면서 다져가야겠습니다.
1. 컴퓨터 퍼포먼스가 높아지면서 많은 사람들이 크게 효율성을 고려하지 않는데,
방심하다 낭패를 볼 수 있기 때문에 정리했다고 하십니다. [출처 : 네이버 블로그 : 하드코더 ]
■ 시간단축을 위한 공간규칙
① Data Structure Augmentation
- 빈번한 연산을 하는데
필요한 시간은 종종 부가적 정보로 데이터 구조를 늘리거나
또는 데이터 구조 내의 정보를
변경하여 더 쉽게 접근할 수 있도록 함으로써
감소시킬 수 있다
② Store Precomputed Results
- 비용이 많이 드는 함수값을 다시
계산하는 비용은 함수를 한번만 계산하고
그 결과를 저장하는 방법으로 감소시킬
수 있다. 그 이후에 함수가 호출될 때는
다시 계산하기보다는 테이블을 검색하여
처리한다.
③ Cashing
- 가장 빈번하게
접근하는 데이터는 그 접근 비용이 가장 적어야 한다.
④ Lazy Evaluation
- 어떤 아이템이 실제로 필요하기 전까지는
평가(evalution)를 하지 않음으로써
아이템에 대한 불필요한 평가를 피한다.
■ 공간절약을 위한 시간규칙 => 메모리 절약
① Packing
- 촘촘한
데이터 구조를 사용하면 데이터를 저장하고 검색하는 데 필요한 시간은
늘어나지만 메모리비용을 줄일 수 있다.
- 패킹은 종종 메모리를 절약하기
위해 시간을 희생하는 것이지만,
때로는 더
작은 데이터 표현으로 인해 처리속도가 더 빨라질 수도 있다.
② Interpreters
- 어떤
프로그램을 표현하기 위한 메모리는 많이 발생하는 일련의 오퍼레이션을
간결하게 표현하는 인터프리터를 사용하여 감소시킬 수 있는 경우가 종종 있다.
-> 인터프리터(interpreter, 문화어: 해석기)는 프로그래밍 언어의 소스 코드를 바로 실행하는 컴퓨터 프로그램 또는 환경을 말한다. 원시 코드를 기계어로 번역하는 컴파일러와 대비된다.
■ 루프
규칙
① Code Motion Out of Loops
- 어떤 계산을 루프가 반복될 때마다
실행하는 것보다는 루프 밖에서 한번만 실행하는
것이 낫다.
② Combining Tests
- 효율적인 내부 루프(inner
loop)는 가장 적은 테스트를 포함하고 있어야하며,
단지 하나의 테스트만을
포함하고 있는 것이 바람직하다. 따라서 프로그래머는
루프의 몇
가지 종료 조건을 다른 종료 조건을 이용하여 시뮬레이트해봐야 한다.
③ Loop Unrolling
- 루프를 펼치는 것은 루프 인덱스 값을
변경해야 하는 비용을 제거하고,
또한 파이프라인
지연(pipeline stall)을 피하도록 도와주며,분기를 감소시키고,
명령어(instruction)
수준의 병렬처리를 증가시킨다.
④ Transfer-Drven Loop
Unrolling
- 만약 내부 루프에서 많은 비용이
단순한 대입문에 소모된다면,
이런 대입문은 코드를 반복하고 변수의 사용을 배경하여 제거할 수 있다.
특히, i=j와같은 대입문을 제거하면, 이와 관련된 코드에서 j가 i 인것처럼 다루어야한다.
⑤ Unconditional Branch
Removal
- 빠른 루프에는 무조건적 분기를 포함하면
안된다.
루프의 마지막 부분에 있는 무조건적 분기는 루프를 회전시켜 루프의 마지막에
조건적
분기를 갖도록 하여 제거할 수 있다.
⑥ Loop Fusion
- 근처에 있는 두 루프가 같은 요소의 집합에
대해 동작하고 있다면,
동작 부분을 묶어 하나의 루프에서 처리하도록 한다.
■ 논리규칙
①
Exploit Algebraic Identities
- 만약 논리식을
평가하는 데 비용이 많이 든다면, 평가하는 데 비용이 덜 드는 대수적으
로 동치인 논리식으로 대체한다.
② Short-Circuiting
Monotone Functions
- 만약 여러변수에 대한 어떤 단조 증가
함수가 특정 한계를 초과하는지 검사하려 할 때,
특정 한계를 한 번 넘은 다음에는 다른 변수에대해서 평가를 할 필요가 없다.
③ Reordering Tests
- 논리식은 비용이 적게 들고 결과가 참인
경우가 많은 식이 비용이 많이 들고 결과가
참인 경우가 거의 없는 식보다 앞에 와야 한다.
④ Precompute Logical
Functions
- 작고 유한한 도메인에 대한 논리 함수는
그 도메인을 나타내는 테이블에 대한 검색으로
대체할 수 있다.
⑤ Boolean Variable
Elimination
- 불리언(Boolean)변수 v에대한 대입식을 v가 참일 때와 거짓일 대를 처리하는
if-else문으로 바꾸어 프로그램에서 불리언 변수를 제거할 수 있다.
■ 프로시저
규칙
① Collasping Function Hierarchies
- 함수가 다른
함수를 호출하는 구조로 되어 있는 경우 호출되는 함수를 인라인화하고
넘겨지는 변수를 묶어서 함수를 재작성하면 종종 실행시간을 단축할 수 있다.
② Exploit Common Cases
- 함수는 모든 경우를 정확히 처리하고 빈번한
경우에 대해서는 효율적으로
처리해야 한다.
③ Coroutines
- 다중 패스(multi-pass)
알고리즘은 종종 협동루틴(coroutines)을 사용하여
단일 패스(single-pass) 알고리즘으로 바꿀 수 있다.
④ Transformations on
Recursive Functions
- 명시적인 프로그램 스택을 사용하여 재귀를
반복으로 변환한다.
- 만약 함수의 마지막 부분에서 자신을 재귀적으로
호출하면, 그 부분을 함수의
첫 번째
부분으로 분기하도록 바꿀 수 있는데, 이것을 Removing Tail Recursion이라고
도 한다.
- 작은 부분문제를 풀 때는 재귀를 반복하여
크기가 0 또는 1 에대한 문제를 풀게 하는 것
보다는 보조 프로시저를 사용하는 편이 더 효율적일 수 있다.
⑤ Paralelism
- 하드웨어가 병렬처리를 지원하는 경우,
프로그램은 가능한 많이 병렬처리를 사용할 수
있는 구조로 작성되어야 한다.
■ 수식 규칙
①
Compile-Time Initialization
- 가능한 많은 변수가 프로그램이 실행되기
전에 초기화되어 있어야 한다.
② Exploit Algebraic
Identities
- 만약 평가하는 비용이 많이 드는 수식이
있다면, 평가하는 데 비용이 적게 들면서
대수적으로 동치인 수식으로 바꾼다.
- 배열의 모든 요소에 대해 반복하는 루프에서
곱셈을 계산하는 데 덧셈을 이용하도록
하면 연산 강도를 줄일 수 있다. 많은 컴파일러가 이 최적화를 수행한다.
이 기법은 점증적 알고리즘의 여러 경우로 일반화 될 수 있다.
③ Common Subexpression
Elimination
- 만약 포함된 변수가 변하지 않은 채로
동일한 수식이 두 번 평가된다면, 첫번째 평가
결과를 저장한 다음 두 번째로 수식을 평가하는 부분에서 사용하여 같은 수식을
두번
평가하는 것을 피할 수 있다.
④ Pairing Computation
- 만약 비슷한 두 수식이 빈번하게 함께
평가된다면, 그 둘을 하나의 쌍으로 평가하는
새로운 프로시저를 만들어야 한다.
⑤ Exploit Word Parallelism
- 비용이 많이 드는 수식을 평가할 때는
컴퓨터 하부 구조가 제공하는 기본적인 데이터
경로의 폭을 최대로 사용하라.
2. 다른 블로그에서 가져온 글인데 이 글은 해외에 있는 15년 이상 경력의 프로그래머가
만든 글을 번역한 글이라고 합니다. 출처는 밑에 남겼습니다.
최적화 VS 가독성. 최적화보단 가독성
코드는 항상 읽기 쉽고 개발자들이 이해할 수 있게끔 작성하라. 읽기 어려운 코드를 읽는데 소모되는 시간과 비용은 최적화로부터 얻을 수 있는 것보다 더욱 크다. 최적화가 필요하다면, DI (의존성 주입)을 사용해 독립적인 모듈로 만들고, 100%의 테스트 커버리지를 유지하여 최소 1년간은 건들지 않아도 되도록 만들어라.
테스트 커버리지 ( 출처 : 조대협의 블로그 )
우리가 단위 테스트나 통합 테스트와 같은 일련의 테스트 작업을 수행하였을때, 이 테스트가 전체 테스트를 해야 하는 부분중에서 얼마만큼을 테스트 했는지를 판단해야 한다.
예를 들어, 20가지의 기능을 가지고 있는 애플리케이션이 있을때, 몇가지 기능에 대해서 테스트를 했는가와 같이, 수행한 테스트가 테스트의 대상을 얼마나 커버했는지를 나타내는 것이 테스트 커버러지이다. 이 커버러지율을 기준으로 애플리케이션이 릴리즈가 가능한 수준으로 검증이 되었는가를 판단하게 된다.
위에서 예를 든것과 같이 기능에 대한 테스트 완료여부를 커버러지의 척도로 삼을 수 도 있다.
좀 더 작은 범위의 테스트인 단위 테스트의 경우는 개개의 클래스나 논리적인 단위의 컴포넌트 각각을 테스트하기 때문에, 테스트에 대한 커버 범위를 각각의 클래스 또는 소스 코드의 각 라인을 척도로 삼을 수 있는데, 테스트가 전체 소스코드중에서 얼마나를 커버했는지를 나타내는것이 "코드 커버러지(Code Coverage)" 이다.코드 커버리지
코드 커버리지(Code Coverage)는 소프트웨어의 테스트를 논할 때 얼마나 테스트가 충분한가를 나타내는 지표중 하나다. 말 그대로 코드가 얼마나 커버되었는가이다. 소프트웨어 테스트를 진행했을 때 코드 자체가 얼마나 실행되었냐는 것이다.
-> 사실 상세한 설명은 아직 이해가 잘 되지 않지만, 가독성이 중요하다는 것을 말해주는 대목 같습니다.
아키텍처 우선
나는 “우리는 빨리 개발을 해야하기 때문에 아키텍처를 설계할 시간이 없다”라고 말하는 사람을 많이 봐왔다. 그리고 그 중 99%가 이러한 생각 때문에 큰 문제를 겪었다.
아키텍처를 생각하지 않고 코드를 작성하는 것은 목표 달성을위한 계획없이 자신의 욕망을 꿈꾸는 것처럼 쓸모가 없다. 코드를 작성하기 전에 먼저 수행 할 작업, 사용 방법, 모듈화 방법, 서비스가 서로 어떻게 동작하는지, 구조가 무엇인지, 테스트 및 디버깅 방법, 업데이트 방법들을 먼저 생각하고 이해해야한다.
-> 저도 코드를 막 짜는 경우가 있는데, 복잡도가 증가할수록 계획 없이 짠 코드는 시간만 낭비하는 경우가 많은 것 같습니다.
테스트 커버리지
테스트는 좋은 일이지만 항상 비용 효율적인건 아니며 프로젝트에 따라 다르다.
테스트가 필요한 경우:
- 최소 한 달간은 건들지 않아도 될 모듈이나 마이크로서비스를 개발하는 경우
- 오픈소스 코드를 작성하는 경우
- 핵심 코드 또는 금전적인 부분과 맞닿는 코드를 작성하는 경우
- 코드를 업데이트 하는 것과 동시에 테스트를 업데이트 할 수 있는 충분한 비용이 있는 경우
테스트가 필요하지 않는 경우:
- 스타트업인 경우
- 팀이 작고 코드가 빠르게 변하는 경우
- 출력값을 보고 간단하게 수동으로 테스트가 가능한 스크립트를 작성하는 경우
나쁜 테스트 코드와 함께 코드를 짜는 것은 테스트가 없는 코드보다 더 위험할 수 있음을 기억하라.
-> 이 파트는 개인적으로 이해가 잘 되지 않네요.간단하고 단순하게
복잡한 코드를 작성하지 말라. 간단하게 작성하면 버그가 줄어들고 디버깅 시간도 줄어들 수 있다. 코드는 수많은 추상화 및 기타 객체지향적인 문제 (특히 Java 개발자와 관련이 있음) 없이 딱 필요한 일만을 수행해야하며, 추후에 간단한 방법으로 코드를 업데이트하기 위해 필요한 것의 20%를 수행해야한다.
-> 어떤 사람은 주석이 없으면 코드는 쓰레기라는 말을 했던 것 같은데, 주석이 없어도 이해가 되야 된다는 것을 강조하네요.
그만큼 쉬운 코드를 짜라는 말이겠죠? 주석보단 구글 API 문서 같은 걸 말하는 것 같습니다.주석
주석은 나쁜 코드를 보여준다. 좋은 코드는 주석 없이도 이해할 수 있어야한다. 그러면 새로운 개발자를 위해 시간을 절약하기 위해 해야 할 일은 무엇인가? — 메서드의 정의와 사용법을 설명하는 한 줄짜리 간단한 문서를 작성하라. 이는 이해를 위한 많은 시간을 절약해 줄 것이며 — 더 많은 사람들에게 메서드를 더 잘 구현할 수 있는 기회를 제공해준다. 또한 이는 글로벌 코드 문서화를 위한 좋은 시작점이 될 것이다.
-> 어떤 사람은 주석이 없으면 코드는 쓰레기라는 말을 했던 것 같은데, 주석이 없어도 이해가 되야 된다는 것을 강조하네요.
그만큼 쉬운 코드를 짜라는 말이겠죠? 주석보단 구글 API 문서 같은 걸 말하는 것 같습니다.강한 결합 VS 느슨한 결합
항상 마이크로서비스 아키텍처를 사용하도록 노력하라. 모놀리틱 소프트웨어는 마이크로서비스 소프트웨어보다 빠르지만, 단일 서버 환경에서만 그렇다. 마이크로서비스는 여러분의 소프트웨어를 여러 서버로의 분산뿐만 아니라 가끔은 하나의 머신에서의 분산처리 (프로세스 분산을 의미한다)도 할 수 있는 가능성을 제공해준다.
-> 어떤 사람은 주석이 없으면 코드는 쓰레기라는 말을 했던 것 같은데, 주석이 없어도 이해가 되야 된다는 것을 강조하네요.
그만큼 쉬운 코드를 짜라는 말이겠죠? 주석보단 구글 API 문서 같은 걸 말하는 것 같습니다.코드 리뷰
코드 리뷰는 좋을 수도 있고 나쁠 수도 있다.
여러분의 팀에 코드의 95%를 이해하고 있고 시간 낭비 없이 모든 업데이트 사항을 모니터링 할 수 있는 개발자가 있는 경우에만 코드 리뷰를 도입하도록 하라. 이 외의 경우에는 단지 시간 낭비가 될 수 있으며 모두가 이를 싫어하게 될 것이다. 이 부분은 많은 질문을 가져오기 때문에 이를 좀 더 깊게 살펴보자.
많은 사람들은 코드 리뷰가 새로운 사람이나 코드의 다른 부분을 작업하는 팀원을 가르치는 좋은 방법이라고 생각한다. 그러나 코드 리뷰의 주요 목표는 코드 품질을 유지하는 것이지 가르치는게 아니다. 여러분의 팀이 원자로 또는 우주 로켓 엔진 냉각 시스템을 제어하는 코드를 작성했다고 가정해보자. 그리고 여러분은 아주 어려운 로직에서 큰 실수를 저질렀고, 이를 새로운 사람에게 코드 리뷰를 위해 제공했다고 해보자. 이 경우 여러분은 사고 위험에 대해 어떻게 생각하는가? — 내 대답은 70% 이상이다.
좋은 팀은 각자가 자신의 역할을 가지고 있으며 일의 한 부분에 대해 완전한 책임감을 갖고 있는 팀이다. 만약 누가 코드의 다른 부분을 이해하고 싶으면 해당 부분을 담당하고 있는 사람에게 찾아가 질문을 하면된다. 모든걸 아는건 불가능하며 전체보다는 코드의 작은 부분을 (하지만 적어도 30%는) 완전히 이해하는것이 더 낫다.
-> 코드 검토(code review)는 코드를 개발자가 작성하고, 다른 개발자가 정해진 방법을 통해 검토하는 일을 말한다. 등위 검사, 제3자 검사라고도 한다.
리팩토링은 작동하지 않는다
나는 일하는 동안 “나중에 리팩토링 할거니까 걱정하지마라”라는 말을 많이 들었다. 그리고 나중에 이는 큰 기술적 부채로 돌아오거나 모든 코드를 다 삭제한 후 처음부터 다시 작성하게 되었다.
따라서 처음부터 여러번 소프트웨어 다시 개발할 수 있는 자금이 있는게 아니라면 부채를 만들지 말라.
-> 처음 제작할 때 부터, 다시 만들 생각을 하지 말자는 취지의 말 같네요.
피곤하거나 컨디션이 좋지 않을때 코딩하지 말라
개발자들이 피곤할 땐 평소보다 2-5배 더 많은 버그와 실수를 만들어낸다. 따라서 과업은 매우 나쁘다. 그렇기 때문에 하루의 업무시간을 약 6시간으로 고려하는 국가가 점점 더 많아지고 있으며, 일부 국가에서는 이미 실천하고있다. 정신적인 일은 육체적인 일을 다루는 것과 같지 않다.
모든걸 한꺼번에 작성하자 말라 - 반복적으로 개발하라
코드를 작성하기 전에 우선 여러분의 고객과 클라이언트가 정말로 필요로 하는걸 분석하고 예측하고, 짧은 기간동안 개발할 수 있는 MVF(Most Valuable Features)를 추려내라. 품질 업데이트를 배포하기 위해 이러한 반복을 사용하도록 하고 무리한 요구사항과 품질 희생에 시간과 자원을 낭비하지말라.
자동화 VS 수동
자동화는 장기적으로 100% 성공이다. 따라서 지금 당장 무언가를 자동화 할 수 있는것이 있다면 바로 하도록 하라. “5 분 밖에 걸리지 않는데, 왜 자동화 해야해?”라고 생각할 수도 있다. 하지만 한 번 계산해보자. 예를 들어 5명의 개발자로 이루어진 팀의 일상적인 작업을 들어보자. 5분 * 5명 * 21일 * 12개월 = 6,300분 = 105시간 = 13.125 일 ~ 5,250$. 직원이 40,000명일 경우엔 비용이 얼마나 커질까?
나가서 취미를 갖자
일의 차별화는 정신 능력을 향상시키며 새롭고 신선한 아이디어를 제공한다. 따라서 잠시 쉬고 신선한 공기를 마시거나 친구들과 이야기를 하거나 기타를 연주하는등의 취미를 가져라.
여유 시간에 새로운걸 배워라
학습을 중단하면 퇴화하기 시작한다.