# 4/21 스터디 ## 임태현 ### 함수형 프로그래밍 1. 선언형 프로그래밍과 함수형 프로그래밍이란? 함수형 프로그래밍은 선언형 프로그래밍에 속하는 패러다임입니다. - 우선, 선언형 프로그래밍은 내부적으로 코드가 어떻게 구현되어있는지, 데이터 흐름이 어떻게 흘러가는지 밝히지 않고 연산과 작업을 표현합니다. - 함수형 프로그래밍은 순수 함수를 사용해 Side Effect를 방지하고 상태 변이를 감소하기 위해 데이터의 제어 흐름과 연산을 추상화해 선언해서 사용하는 패러다임입니다. ```javascript= // 명령형 프로그래밍 function odds(n) { const res = []; for(let i=1; i<=n; i++) { if(i%2) res.push(i); } return res; } // 함수형 프로그래밍 function odds(n) { return _.range(1, n).filter(n => n%2); } ``` 2. 순수 함수가 뭐에요? 호출될 때 Side Effect가 없는 함수입니다. 함수의 실행부를 통해 해당 Scope외의 다른 데이터에 영향을 주지 않고 오직 반환값만을 사용하기 위해 존재합니다. 이 순수 함수는 항상 같은 입력을 주면 같은 결과를 가져오는 함수형 프로그래밍의 가장 원초적인 개념이라고 볼 수 있습니다. Side Effect가 없는 순수 함수는 테스트하기 편하고 로직 전체를 파악하는게 쉬워집니다. 3. Side Effect는 어떤 것들이 있나요? 1. 전역 변수를 변경하는 경우 2. 함수의 인자의 속성을 변경하는 경우 3. 사용자 입력을 받는 경우 4. 예외를 catch하지 않고 throw하는 경우 5. 화면이나 파일로 무언가를 출력하는 경우 6. DB나 쿠키 등 외부의 것에 접근하는 경우 7. JavaScript같은 경우 this가 동적이기 때문에 this 호출도 포함 4. 선언형 프로그래밍과 명령형 프로그래밍의 차이 1. 명령형 프로그래밍은 알고리즘을 명시하지만 목표를 명시하지 않습니다. 그리고 선언형 프로그래밍은 그 반대로 목표를 명시하고 알고리즘을 명시하지 않습니다. 2. 명령형 프로그래밍은 상태를 변경시키는 과정을 이어나가는 패러다임이며, 선언형 프로그래밍은 과정을 중요시 하지 않고 결과들을 합쳐나가는 패러다임입니다. 5. 함수형 프로그래밍과 객체지향 프로그래밍의 장단점 - 객체지향의 장점으로, 기준이 객체이기 때문에 무언가 형태를 띄운 상태에서 조작을 하며, 상태를 가지고 있는 것이 현실 세계와 비슷하기 때문에 이해가 쉽습니다. - 하지만 객체간의 의존관계가 복잡해지면 의도치 않는 상태나 결과가 발생할 수 있습니다. - 이에 반면 함수형 프로그래밍은 Side Effect를 없앤 순수 함수를 기준으로 프로그래밍을 진행하기 때문에 의도치 않는 결과를 최소화 시키면서 테스트가 쉬워집니다. 그리고 기능들을 작은 단위로 모듈화하고 큰 기능을 작은 함수들로 구성하는 등 재사용성이 큽니다. - 하지만 수학적인 접근을 해야하기 때문에 진입장벽이 높고 함수형 패러다임을 지원하기 힘든 언어에선 보다 로직이 복잡해보일 수 있다. 6. 커링이란? 함수의 인수들을 여러번의 호출로 함수를 지연 평가를 할 수 있게 하는 함수형 프로그래밍 기법입니다. 저같은 경우 함수형 라이브러리의 curry함수를 사용해 일반 함수형 함수들을 Wrapping해줘 지연 평가할 수 있게 합니다. 하지만 보통 함수형 프로그래밍을 지원하는 함수형 라이브러리는 기본적으로 함수들이 currying되어 있습니다.(ex. loadash/fp, ramda) ```python= def sum(a): def sum1(b): def sum2(c): return a+b+c return sum2 return sum1 print(sum(1)(2)(3)) # 6 ``` ```javascript= const users = [{ name: '태현', age: 25 }, { name: '예지', age: 26 }, { name: '지수', age: 25 }]; const getNamesForAge = age => _.pipe( _.filter(v=> v.age === age), _.pluck('name'), ); const printAtDOM = curry((selector, html) => { document.querySelector(selector).innerHTML = html; }); const printNamesOverAge25 = _.go( users, getNamesForAge(25), _.map(v => `<li> ${v} </li>`) _.join(''), (list) => `<ul> ${list} </ul>`, printAtDOM('#user-list') ); const curry = f => function recur(...args) { return args.length < f.length ? (...args2) => recur(...args,...args2) : f(...args) }; const sum = curry((a,b,c) => a+b+c); const sum10And25 = sum(10, 25); console.log(sum10And25(20)) // 55 const sum20 = sum(20); console.log(sum20(30)(50)) // 100 ``` - 커링과 부분 적용의 차이 - 커리된 함수자체가 이미 부분 적용된 함수 - 커링은 부분 호출할 때마다 단항 함수를 중첩적으로 합성해 결과를 낸다. 그래서 여러 인수를 부분 평가를 하면서 평가 시점을 조절할 수 있다. - 부분 적용은 함수 인수를 미리 바인딩 시킨 함수를 만드는 것 7. 지연 평가의 의미와 예시 함수들을 합성하는데, 필요한 시점까지 평가(실행, 호출)를 미루는 것입니다. 만약 n개의 요소를 가진 리스트나 배열 `arr = new Array(n)` 에서 가공을 한 뒤, 1개의 요소만을 반환하고 싶은 상황이 있습니다. 지연평가를 하지 않을땐, ```javascript= [1,2,3].map(v => v+1).map(v=>v*2).map(v =>).take(1) arr.map(함수1) [ → ] // n번 .map(함수2) [ → ] // n번 .map(함수3) [ → ] // n번 .take(1) ``` 각 요소들이 임의의 함수에 모두 n번 평가된 뒤, 다음 함수에 평가 되는 과정을 거칩니다. 만약 1개 또는 적은 개수만을 가지고 싶어도 n번, n번...을 거칩니다. 하지만 1개만을 원할 때 지연평가하면 ```javascript= arr.map(함수1) // 1번 ↓ .map(함수2) // 1번 ↓ .map(함수3) // 1번 ↓ .take(1) ``` 1번,1번...1번을 거쳐 CPU 부하가 줄어듭니다. 즉 지연평가는 이어진 함수들은 동작을 하지 않은채로 `take(1)` 같이 실행하는 함수들이 호출될 때 실행되게 됩니다. 이 때, 지연평가를 하면 한 요소씩 모든 함수를 거치게 되는데 수직적인 데이터 흐름입니다. 반면, 지연평가를 하지 않으면 모든 요소가 임의의 함수를 거쳐야 다음 함수가 호출하는 수평적인 데이터 흐름이 발생합니다. 8. RxJS, RxJava같은 ReactiveX 라이브러리와 lodash와 ramda같은 라이브러리의 차이 ReactiveX는 리액티브와 함수형이 합쳐진 프로그래밍 패러다임입니다. 리액티브 프로그래밍은 데이터 흐름과 변경 전파를 한단계의 추상화를 거쳐 비즈니스 로직을 작성이 수월해집니다. 이 때, 옵저버블이란 개념을 사용해 스트림을 객체화 시킨 뒤, 비즈니스 로직을 작성합니다. 그리고 옵저버 패턴을 사용해 구독으로 스트림을 실행합니다. 그 다음, 비즈니스 로직 작성에 함수형 프로그래밍을 입히는 것은 Optional하지만 이는 비동기나 이벤트같은 스트림을 쉽게 제어합니다. 결론을 내면, 데이터 흐름을 추상화시킨 뒤, 함수형 함수를 이용하는 것이 ReactiveX이며 Rx시리즈는 이를 구현한 도구입니다. lodash, ramda는 유틸성을 포함해, 순수하게 함수형 프로그래밍을 도와주는 도구입니다. <!-- 9.함수형 프로그래밍을 적용해본 경험 10. Monad란? 11. Kleisli Composition 관점에서의 Monad --> ### 객체지향 설계 정리 https://limkydev.tistory.com/77 여기가 정말 잘되있네 ### 객체지향 설계 질문 및 답변 1. OOP를 설계하는데 중요한 원칙을 아시나요? 제가 알고 있는 객체지향 설계에서 SOLID라는 원칙 5가지를 알고 있습니다. 이 원칙들을 사용해서 객체 내부의 응집도를 높이고, 객체 외부간의 결합도를 낮추는 것이 궁극적인 목표로 알고 있습니다. 2. 왜 객체 내부의 응집도를 높이고 외부간의 결합도를 낮추는 것을 추구할까요? 프로젝트의 관리에 사용하는 인력을 줄일 수 있기 때문입니다. 제가 생각하는 소프트웨어 구성에 있어서 가장 중요한 것은 유지 보수라고 생각합니다. 무언가를 서비스할 때 기능을 시간내에 구현하는 것도 중요하다고 생각하지만, 미래를 보았을 때 수정을 용이하게 하는 것이 확장면에서 보다 손실을 줄여주기 때문입니다. 그런 면에서 재사용성을 높이고 수정을 최소화하는 것이 기능을 추가할 때나 수정할 때 접근해야하는 영역이 줄어들기 때문에 손실을 줄여주는 기대효과가 생긴다고 생각합니다. 3. Solid가 무엇인지 간단하게 설명해주실래요? 1. 첫번째로 단일 책임 원칙이 있습니다(SRP). 이 원칙은 객체의 책임을 최소화해 수정과 보완할 것을 줄여줍니다. 2. 두번째로 개방 폐쇠 원칙입니다(OCP). 임의의 객체에 대해서 내부에서만 사용하는 기능 수정에는 관대하지만 외부 객체와 의존하는 기능은 interface 등을 사용해 제한하는 것입니다. 3. 세번째로 리스코프 치환 원칙입니다(LSP). 부모와 자식간의 관계를 논리적으로 잘 풀어서 설계하는 원칙입니다. 즉, 상속을 통해서 확장을 하기 때문에 자식 객체의 Upcasting이 문제가 없어야 합니다.(아버지<->아들 x, 포유류 <-> 고래 o) 4. 네번째로 인터페이스 분리 원칙입니다(ISP). 각 객체의 역할에 필요 없는 것을 줄여주기 위해, interface로 책임 단위를 최소화시키고 객체마다 필요한 책임들을 묶는 것입니다. 5. 다섯번째로 의존성 역전 원칙입니다(DIP). 상위 클래스가 하위 클래스를 의존하면 안된다는 것입니다. 왜냐하면 하위 클래스일수록 사용이 많아지고 그에 따라 변화가 많아집니다. 그렇게 된다면 하위 클래스가 변화할 때마다 상위 클래스의 수정될 필요가 많아집니다. 4. Solid에서 가장 중요하게 생각하는 원칙이 무엇인가요? 저는 단일 책임의 원칙이 가장 중요하다고 생각합니다. 왜냐하면 다른 원칙들에 근본이 되는 원칙이기 때문입니다. 객체지향은 객체를 주체로 설계를 합니다. 각 객체는 다른 객체와 상호작용을 하는 것을 염두로 프로그래밍을 이끕니다. 그래서 각 객체간 의존성을 필요로 합니다. 하지만 한 객체당 영향을 주는 객체가 많아지면 내부적인 흐름이 어려워지면서 복잡도가 증가하게 됩니다. 그래서 이런 복잡도를 줄이기 위해 각 책임을 최소화 시키고 그것들을 작은 단위로 확장하게 된다면 필요없는 코드가 줄어들고 좀 더 코드 내에서 의존 관계를 확인할 수 있을 것 같습니다. 5. 실제로 객체 중심적으로 설계할 때 어떻게 하는가? 저는 객체 중심적으로 생각할 때는 주로, 프로젝트 설계할 때 DB 스키마를 구성할 때와 프론트에서 컴포넌트를 구성할 때입니다. - DB는 보통 MySQL을 사용했기 때문에 ERD를 이용해 테이블을 객체로 생각하고 관계를 그리면서 생각했습니다. 우선 사용자들에게 입력받는 데이터들이 무엇이 있을까 생각을 하면서 테이블을 그린 뒤, 서로간의 n:m 관계를 생각했습니다. 그리고 필요에 따라 DB 접근할 때 불필요한 열들이 있으면 정규화를 진행했습니다. - 그리고 프론트에서 컴포넌트들을 설계할 때 우선 프로토타입으로 만들어진 것을 기준으로 Page 단위로 나눈 다음, Page내에서 서버로 API를 요청보낼 때 필요한 객체들을 중심으로 생각했습니다. 왜냐하면 협업에 직관적인 데이터들을 먼저 설계를 하고 다음으로 이어가는 것이 더 효율적으로 판단했기 때문입니다. 그 다음, 각 페이지마다 사용하는 객체들을 나열한 뒤, Store에서 관리하기 위해 Top-down 방식으로 설계했습니다. 그 다음 Loading 같은 상태를 고려하고 그외에 필요한 입력데이터 같은 것들을 컴포넌트내의 상태로 분리했습니다. --- ## 최지수 ### [ 웹 흐름 ] #### Web Server - 요청이 들어오면 전달하는 역할 - 정적인 컨텐츠들을 제공한다. ex) Nginx, apache #### Web Server Gateway Interface (WSGI) - 파이썬 어플리케이션이 웹 서버와 통신하기 위한 인터페이스 - 웹 서버와 웹 애플리케이션(Django)간의 연결을 중계한다. - 웹 서버는 파이썬을 모르기 때문에 wsgi가 http 요청을 파이썬으로 바꿔주고, django로 부터 받은 응답을 nginx가 알 수 있도록 바꿔준다. ex) gunicorn, uwsgi `Client -> Web Server -> WSGI -> Application`의 형태로 동작한다. #### Web Application Server (WAS) - 웹 서버 위에 어플리케이션을 얹은 것이다. - DB와 연결하여 동적인 컨텐츠를 제공한다. ### [ 로드 밸런싱 ] #### 개념 부하분산 또는 로드 밸런싱은 컴퓨터 네트워크 기술의 일종으로 중앙처리장치 혹은 저장장치와 같은 컴퓨터 자원들에게 작업을 나누는 것을 의미한다. 서버에 가해지는 부하(=로드)를 분산(=밸런싱)해주는 장치 또는 기술이다. 사업의 규모가 확장되고, 클라이언트의 수가 늘어나게 되면 기존 서버만으로는 정상적인 서비스가 불가능하게 되는데, 이런 증가한 트래픽에 대처할 수 있는 방법은 크게 두 가지다. - Scale up : 서버 자체의 성능을 높이는 것 - Scale out : 여러 대의 서버를 두는 것 Scale-out의 방식은 여러 대의 서버로 트래픽을 균등하게 분산해주는 로드밸런싱이 반드시 필요하다. #### 로드 밸런싱 알고리즘 - 라운드로빈 서버에 들어온 요청을 순서대로 돌아가며 배정하는 방식 서버와의 연결이 오래 지속되지 않는 경우 적합하다. - 가중 라운드로빈 방식 각 서버에 가중치를 매기고 가중치가 높은 서버에 요청을 우선적으로 배정하는 방식 서버의 트래픽 처리 능력이 다른 경우 사용한다. - 최소 연결 방식 요청이 들어온 시점에 가장 적은 연결 상태를 보이는 서버에 트래픽을 배정하는 방식. 서버에 분배된 트래픽들이 일정하지 않은 경우에 적합하다. - IP 해시 방식 클라이언트의 IP주소를 특정 서버로 매핑하여 요청을 처리하는 방식 사용자가 항상 동일한 서버로 연결된다. #### L4 로드 밸런싱 - 트랜스포트 계층에서 로드를 분산한다. - TCP, UDP 포트 정보를 바탕으로 한다. - 데이터 안을 보지 않고 패킷 레벨에서만 로드를 분산하기 때문에 속도가 빠르고 효율이 높음 - 섬세한 라우팅이 불가능하지만 L7로드 밸런서보다 저렴하다. #### L7 로드 밸런싱 - 애플리케이션 계층에서 로드를 분산한다. - HTTP 헤더, 쿠키 등과 같은 사용자 요청을 기준으로 특정 서버에 트래픽을 분산하는 것이 가능하다. 즉, 패킷 내용을 확인하고 그 내용에 따라 로드를 특정 서버에 분배하는 것이 가능 특정 기능을 하는 요청이 들어오면 그 요청을 처리하는 서버로 보내는,,, - 더 섬세한 라우팅이 가능하고, 비정상적인 트래픽을 필터링 할 수 있다. - 패킷의 내용을 복호화 해야하기 때문에 더 많은 비용이 든다. --- ## 문예지 ### Spring Triangle 1. IoC(Inversion Of Control) - 제어 역전 - 의존성 주입(DI)를 통해 이루어짐 ```java= class OwnerController { //의존성 직접 생성 private OwnerRepository repo = new OwnerRepository(); } ``` ```java= class OwnerController { // 오너 레포지토리를 사용하지만 직접 만들지 않음 private OwnerRepository repo; // 생성자를 통해 받음 -> 오너 컨트롤러가 하는 일이 아님 public OwnerContoller(OwnerRepository repo) { this.repo = repo; } } ``` 인스턴스 관리 주체가 OwnerController가 아닌 외부에 있어서 제어 역전이라고 부름 2. AOP(Aspect Oriented Programming) - 관점 지향 프로그래밍 - 부가기능적 측면에서 바라보았을 때 공통된 요소를 추출하는 것 - 가로 영역의 공통된 부분을 잘라냈다고 하여 크로스 컷팅이라고도 불림 - 프록시 패턴과 관계있음 <br> > 장점 > - 어플리케이션 전체에 흩어진 공통 기능이 하나의 장소에서 관리 - 다른 서비스 모듈들이 본인의 목적에만 충실하고 그 외 부분은 신경쓰지 않아도 됌 <br> > AOP & OOP - AOP: 인프라 혹은 부가기능의 모듈화 - OOP: 비지니스 로직의 모듈화 4. PSA(Portable Service Absraction) - 서비스 추상화 - 환경의 변화와 관계없이 일관된 기술로의 접근 환경을 제공하려는 추상화 구조 - 특정 기술에 직접 영향에 받지 않게 객체를 POJO 기반으로 한번씩 더 추상화한 Layer를 갖고 있으며 이를 통해 일관성 있는 서비스 추상화를 만들어냄 > POJO? > - Plain Old Java Object - 특정 규약에 종속되지 않음 - 코드의 간결함(비지니스 로직과 특정 환경에 종속적인 코드를 분리하므로) - 자동화 테스트에 유리(환경에 종속적이지 않음) ```java= public class ExampleListener implements MessageListener { public void onMessage(Message message) { if (message instanceof TextMessage) { try { System.out.println((TextMessage) message).getText(); }catch (JMSException ex) { throw new RuntimeException(ex); } } else { throw new IllegalArgumentException("Message type error"); } } } ``` ```java= @Component public class ExampleListenerPojo { @JmsListener(destination = "myDestination") public void processOrder(String message) { System.out.println(message); } } ``` ### OS **1. 프로세스** > 프로그램 vs 프로세스 > - 프로그램: 실행코드 - 프로세스: 메모리상에서 실행 중인 작업 단위 > 프로세스의 상태 > - 커널에서 준비 큐, 대기 큐, 실행 큐 등을 이용하여 상태관리 1. 생성(create): 프로세스가 생성되는 중 --- 2. 실행(running): 프로세스가 CPU를 차지하여 명령어들이 실행 중 3. 준비(ready): 프로세스가 CPU를 사용하고 있지는 않지만 언제든지 사용할 수 있는 상태, CPU가 할당되기를 기다리는 중 4. 대기(waiting/block): 프로세스가 입출력 완료, 시그널 수신등 이벤트를 기다리고 있는 상태 --- 5. 종료(terminated): 프로세스 실행이 종료 <br> > 상태전이 > 1. dispatch 준비 큐에 있던 프로세스가 cpu를 점유하는 것 ``` dispatch (processname) : ready -> running ``` 2. block(보류) 실행 상태의 프로세스가 허가된 시간을 다 쓰기 전에 입출력 동작을 필요로 하는 경우 프로세스는 CPU를 스스로 반납하고 보류 상태로 넘어감 ``` block (processname) : running -> blocked ``` 3. wakeup 원하는 이벤트가 일어났을 때 보류 상태에서 준비 상태로 넘어가는 과정 ``` wakeup (processname) : blocked -> ready ``` 4. timeout 프로세스가 프로세서를 독점해서 사용하지 못하게 clock interrupt를 둬서 프로세스가 일정 시간동안만 프로세서를 점유하게 함 ``` timeout (processname) : running -> ready ``` <br> **2. 작업 스케줄링** > 단계 > - 1단계 스케줄링(작업/승인 스케줄링) 어느 작업부터 시스템 내 자원들을 사용하게 할지 결정 - 2단계 스케줄링 어느 프로세스부터 CPU를 차지할 수 있게 할지 결정 - 3단계 스케줄링 CPU가 사용가능한 경우 어느 프로세스에게 배당할지 결정 > 결정 시점 > - running -> block - running -> ready - block -> ready - running -> terminate > 비선점형 vs 선점형 > **비선점형** - 어떤 프로세스가 CPU를 할당받으면 그 프로세스가 종료되거나 입출력 요구가 발생하여 자발적으로 중지될 때까지 계속 실행되도록 보장 ##### 장점 - 순서대로 처리 - 응답시간 예상 가능 - 선점방식보다 스케줄러 호출 빈도가 낮고 오버헤드가 적음 ##### 단점 - CPU 사용 시간이 긴 프로세스가 짧은 여러 프로세스를 오랫동안 대기시켜 처리율이 떨어질 수 있음 **선점형** - 어떤 프로세스가 CPU를 쓰고 있어도 다른 프로세스가 실행 중인 프로세스를 중시시키고 강제로 점유 가능 ##### 장점 - 모든 프로세스가 CPU 사용 시간을 동일하게 할당받을 수 있음 - 긴급한 프로세서 제어가능 ##### 단점 - 오버헤드가 크다 > 정적 스케줄링 vs 동적 스케줄링 > **정적 스케줄링** 프로세스에 부여된 우선순위가 바뀌지않음 **동적 스케줄링** 스케줄링 과정에서 프로세스의 우선순위를 변동시킴