Skip to content

소프트웨어의 핵심 비기능 요구사항

글의 목적

프로라면 어떤도구를 사용하든 기능 요구사항은 당연히 구현가능해야 된다고 생각한다. 문서로는 소프트웨어를 완변하게 설계했지만, 소프트웨어가 의도한 데로 동작하지 않다면 문서를 작성한 노력과 소프트웨어 개발한 노력은 인정받기 힘들다.

소프트웨어의 품질을 높이기 위해서는 비기능 요구사항이 충족해야 한다. 비기능 요구사항은 개발이나 유지보수, 운영, 컴퓨터 리소스의 효율적 활용에 커다란 영향을 미친다. 배포 후 운영 시 발생하는 장애의 대부분 비기능적인 특성에 기인한다.

그만큼 중요한 사항임에도 비기능은 기능에 비해 경시되고 있으며 이를 별도로 주목하지 않고 뒤로 미루려는 경향이 있다. 비기능 요구사항의 관점들이 무엇이 있는 지 정리하기 위해 작성된 포스트다.

비기능 요구사항

비기능 요구사항이란 기능 외적인면 전반에 관한 요구사항을 뜻한다. 비기능 요구사항의 관점에는 다음과 같은 것들이 있다.

  1. 변경 용이성
  2. 상호 운용성
  3. 효율성
  4. 신뢰성
  5. 테스트-용이성
  6. 재사용성

1. 변경 용이성

변경 용이성이란 소프트웨어를 얼마나 쉽게 개선할 수 있는지에 대한 능력을 뜻한다. 구체적으로 소프트웨어를 쉽게 수정/확장/재조직, 다른 플랫폼으로 이식 할 수 있는지 등의 능력을 뜻한다.

소프트웨어가 처음 배포된 채 그대로 사용되는 경우가 없다. 소프트웨어는 기존의 요구사항은 변경되고 새로운 요구사항이 추가된다. 사용자의 요구사항을 장기간에 걸쳐 세심하고 빠르게 대응하려면 코드를 변경하기 쉬운 아키텍쳐가 필요하다.

변경 용이성을 위한 좋은 아키텍처 설계 핵심은 소프트웨어의 어느 부분이 변경에 대해 높은 유연성을 지녀야 하는 지, 반대로 변경이 발생하지 않는 부분이 어디인지를 파악하는 것이다. 그런 다음 유연성을 지녀야 할 부분은 변경을 고려한 설계를 지원하는 패턴을 사용하므로써 유연성을 높여야 한다.

브라우저 저장소 사용하는 경우
js
const BROWSER_STORAGE = sessionStorage
const setItem = (key, value) => {
  BROWSER_STORAGE.setItem(key, JSON.stringify(value))
}
const getItem = (key) => {
  return JSON.parse(BROWSER_STORAGE.getItem(key))
}
js
const IS_OPENED = 'isOpened'
const DATE_IDS = 'dateIds'

setItem(IS_OPENED, true)
setItem(DATE_IDS, [1, 2, 3])

getItem(IS_OPENED) // true
getItem(DATE_IDS) // [1, 2, 3]

2. 상호 운용성

상호 운용성이란 소프트웨어가 다른 소프트웨어와 정보를 주고 받을 수 있는 능력을 뜻한다. 소프트웨어 간의 같은 교환 형식을 매개로 데이터를 주고 받거나, 같은 형식의 파일을 읽고 쓰거나, 같은 프로토콜을 사용함으로써 서로 연결 할 수 있는 능력이 요구된다.

소프트웨어는 시스템의 일부이며 독립해서 존재하는 것이 아니다. 다른 시스템이나 환경과 빈번하게 상호작용한다. 다른 소프트웨어와 연결할 수 있다는 것은 기존 자산을 그대로 활용할 수 있다는 뜻이다.

게다가 신규로 개발할 소프트웨어가 줄어든다는 효과도 있다. 따라서 연결성이 좋다는 것은 소프트웨어 용도의 선택지를 크게 확장하고 개발 기간 단축비용의 대폭 삭감으로 이어진다. 즉, 연결의 용이성은 소프트웨어의 가치를 높인다.

3. 효율성

효율성이란 소프트웨어가 실행되면서 동반되는 리소스 사용에 있어서 적절한 성능을 끌어내는 능력을 뜻한다. 효율성은 크게 2가지 관점으로 나뉜다.

첫째, 시간이라는 관점에서 리소스의 사용 효율을 정의한다. 일정 시간 내에 처리를 몇건 끝낼 수 있는 지를 뜻하는 처리율, 사용자의 입력조작 부터 응답까지 걸리는 시간인 응답시간, 사용자의 작업 개시로 부터 요구받는 정보의 출력을 끝내기까지 걸리는 시간인 소요시간 등으로 계측한다.

둘째, 컴퓨터 자원이라는 관점에서 리소스의 사용 효율을 정의한다. CPU 사용시간이나 메모리 사용량, 스토리지 소비량, 네트워크 전송량 등으로 계측한다.

효율성을 높이려면 성능 최적화가 필요하다. 기본적으로 정적 리소스는 웹팩의 트리 쉐이킹코드 스플리팅을 활용하여 최적화 할 수 있다. 네트워크 속도는 현재 필요한 정보만 요청하거나 병렬 요청 가능한 부분을 병렬로 바꾸면 향상 시킬 수 있다.

js
// Not Cool
async () => {
  const data1 = await requestData1();
  const data2 = await requestData2();
  const data3 = await requestData3();
}

// Cool
async () => {
  const [data1, data2, data3] = await Promise.all([
    requestData1, requestData2, requestData3
  ]);
}
트리 쉐이킹

트리 쉐이킹은 사용하지 않는 코드를 제거하는 방식이다. ES6 모듈의 구문에 의존하여 의존성 트리를 만들고 사용하지 않는 코드를 삭제한다. 사이트 이펙트를 일으키는 코드는 제거되지 않고 남겨준다. 웹팩에 ModuleConcatenationPlugin와 같은 서드 파티 도구를 사용해야 한다.

코드 스플리팅

자바스크립트를 청크로 분할하고 청크를 필요로 하는 애플리케이션의 경로에만 청크들을 배분하여 성능을 개선하는 기술이다. 중복 코드 방지를 위해 SplitChunksPlugin를 사용하여 공통 의존성을 다른 청크파일로 분리한다. 지연 로딩을 하기 위해서는 ES Proposal에 있는 import()를 사용하면 청크를 만들게 된다.

4. 신뢰성

신뢰성이란 소프트웨어가 예외적인 상황 혹은 예기치 못한 방법이나 부정한 방법으로 사용된 상황에서도 기능을 유지하는 능력을 뜻한다. 신뢰성에는 2가지 측면이 있다.

첫째, 결함 허용력으로 소프트웨어에 장애가 발생했을 때 정상적인 동작을 계속 유지하는 능력이다. 예외 발생에 대해서 올바른 행위를 보증하는 한편 내부적으로 복구를 실시해야 한다. 대책으로 장애 발생 시에는 제공하는 기능을 한정하고 중요한 기능만을 제공해서 처리의 지속성을 우선시하는 설계도 고려해야 한다.

둘째, 견고성으로 부정한 사용 방법이나 입력 실수로부터 소프트웨어를 보호하는 능력이다. 다양한 사용 방법에 대해 시스템 차원에서 정의된 상태로 이행한다. 대책으로 장애 발생 시에 해당 부분을 분리하는 설계와 사용자가 잘못된 조작을 해도 안전하게 가동할 수 있는 설계를 고려해야 한다.

그리고 장애 발생 시 인지가 필요하다. 장애 발생 시 담당자가 인지와 추적을 할 수 있도록 하는 도구인 Sentry를 사용하는 것을 추천한다.

5. 테스트 용이성

테스트 용이성이란 소프트웨어에 대해 효과적이면서 효율적으로 테스트를 수행하는 능력을 뜻한다. 테스트가 효과적이라는 말은 테스트가 깊이 있고 질이 높다는 뜻이다.

소프트웨어의 규모와 복잡성이 증가 할 수록 테스트는 더욱 난해해지고 비용이 많이 든다. 따라서 소프트웨어가 원활하게 동작할 뿐만 아니라 테스트를 쉽게 수행할 수 있는 아키텍처가 요구된다.

테스트 용이성을 위해 설계할 때는 모듈 간 종속 관계를 제거하는 것이 핵심이다. 종속 관계가 있으면 테스트하기 어려운 부분을 만났을 때 테스트 전체가 발목이 잡힐 가능성이 커진다. 최대한 종속 관계를 제거하고 작은 단위로 테스트가 가능해지도록 설계해야 한다.

6. 재사용성

재사용성이란 소프트웨어를 전체나 일부를 다른 소프트웨어 개발에 재사용하는 능력을 뜻한다. 재사용성에는 2가지 측면이 있다.

첫째, 재사용하는 소프트웨어 개발로 프로젝트 내의 기존 모듈, 예전 프로젝트의 모듈, 각종 라이브러리 등을 이용하는 것을 의미한다. 재사용 가능한 모듈을 개발 중인 소프트웨어에 그대로 혹은 변형해서 통합한다.

둘째, 재사용을 위한 소프트웨어 개발로 장래의 프로젝트에서 재사용할 수 있는 모듈을 현재 소프트웨어 개발 과정에서 창출하는 것을 의미한다. 다른 소프트웨어에서 재사용하기 위한 소프트웨어를 개발한다.

재사용하는 소프트웨어를 개발할 때는 아키텍처의 구성을 기존 구조나 모듈에 플러그인 할 수 있도록 한다. 여기에는 소프트웨어 컴포지션을 지원한다는 목적이 있다. 소프트웨어 컴포지션이란 소프트웨어를 기존 모듈을 가지고 조립하는 것이다.