Haskell 패키지 개발 전반을 아우르는 실용적인 체크리스트입니다. 소스 관리, 호스팅, 빌드/정의 도구, 버전 규칙과 라이선스, 문서화와 변경 로그, 의존성과 모듈 설계, 경고 해결과 코드 스타일, 테스트/CI/벤치마크, 릴리스 자동화까지 모범 사례와 팁을 제공합니다.
이 글은 원래 블로그 게시물입니다. 매우 주관적이며 최신으로 유지되지 않습니다. 이 Reddit 토론이나 이 토론에 관심이 있을 수 있습니다.
이 체크리스트는 Haskell 패키지를 개발하는 데 알아야 할 모든 것을 다룹니다. Cabal 사용자 가이드는 좋은 저수준 정보를 제공하고, @sebastiaanvisser의 “더 나은 Haskell 패키지를 향하여” 글은 훌륭한 고수준 지침을 제공합니다. 이 체크리스트는 둘 다를 포괄하며 그 사이의 빈틈도 메웁니다. @tfausak의 “Haskell 패키지 체크리스트”에서 보완·각색했습니다.
이 항목들 대부분을 충족하는 출발점을 찾고 있다면 Haskeleton Stack 템플릿을 고려해 보세요. 훌륭한 기반을 제공합니다. 가이드라인을 따르는 실제 패키지를 찾는다면 Rocket League 리플레이 파서/제너레이터인 Rattletrap을 살펴보세요. 여기서 일부 항목이 실제로 어떻게 구현되는지 정확히 볼 수 있습니다.
소스 관리에 없으면 존재하지 않는 것과 같습니다. Git이 가장 인기 있는 선택이지만 인터페이스가 이해하기 어려울 수 있습니다. GitHub Desktop 같은 GUI를 사용하는 것을 고려하세요.
GitHub은 다른 개발자들이 패키지에 쉽게 기여할 수 있도록 해 줍니다. 다른 호스팅에 비해 기여를 받을 가능성이 높습니다. 또한 GitHub은 많은 다른 서비스와 잘 통합됩니다.
Stack은 Haskell 의존성을 수월하게 관리합니다. GHC를 설치해 주고 실제로 동작하는 빌드 플랜을 보장합니다. 오늘 패키지가 빌드된다면, Stack은 내일도 계속 빌드되도록 보장합니다.
기본 Cabal 패키지 파일 포맷은 독자적이고 번거로우며 장황합니다. hpack은 YAML을 사용하고 불필요한 상용구를 피합니다. Stack은 hpack을 통합하여 필요할 때 package.yaml을 *.cabal 파일로 자동 변환합니다.
혼란을 피하려면 전부 소문자를 사용하세요. 누군가 실제로는 HTTP를 의미했는데 http를 설치하려고 하는 상황을 만들고 싶지 않을 겁니다. 단어는 하이픈으로 구분하되, 짧고 기억하기 쉽게 유지하세요. 아무도 hypertext-transfer-protocol처럼 길게 타이핑하고 싶지 않습니다.
불행히도 Hackage는 패키지 버전 정책(PVP)을 권장합니다. PVP는 두 개의 주 버전 번호를 사용하여 모호함을 더합니다. 그 결과 패키지들이 주 버전 0에 머물도록 만드는 경향이 있는데, 이는 신뢰감을 주지 못합니다. 많은 다른 언어는 시맨틱 버저닝을 사용합니다. SemVer는 개발자가 일반적으로 버전 번호를 생각하는 방식과 잘 맞습니다.
라이선스가 없으면 아무도 패키지를 사용할 수 없습니다. Haskell에서 가장 인기 있는 라이선스는 BSD 3-Clause이고, 그다음이 MIT입니다. 어떤 라이선스를 선택하든, 라이선스 파일을 패키지에 포함하세요.
대부분의 사람들은 README를 읽으며 패키지를 익힙니다. 패키지가 해결하는 문제를 설명해야 합니다. 최소한 하나의 구체적인 예제를 포함하세요. GitHub은 README.markdown을 자동으로 포맷합니다.
대부분의 패키지는 릴리스를 표시하기 위해 Git 태그를 사용하지만, 변경 사항을 찾기 위해 diff를 읽게 하는 것은 사용자에게 적절한 방법이 아닙니다. 변경 사항의 사람 친화적인 요약을 GitHub 릴리스에 적으세요. 그런 다음 CHANGELOG.markdown에서 그곳으로 링크하세요.
synopsis는 검색하거나 패키지를 볼 때 표시됩니다. 짧고, 명령형이며, 설명적으로 유지하세요. 또한 description도 작성하세요. synopsis와 거의 동일해도 괜찮습니다. 예:
name: aeson
synopsis: Encode and decode JSON.
description: Aeson encodes and decodes JSON.
패키지 동작에 필요한 경우에만 의존성을 추가하세요. 있으면 편하다는 이유만으로 포함하지 마세요. 예를 들어, 코드를 더 쉽게 작성하게 해 주더라도 아마 lens는 피해야 할 것입니다. 무거운 의존성을 피하는 것뿐 아니라, 의존성 개수 자체도 너무 많지 않게 하세요. 완전히 처음부터 시작해 패키지를 설치하는 데 얼마나 걸릴지 생각해 보세요.
필요한 파일은 extra-source-files에 포함하세요. 테스트와 벤치마크에 필요한 파일도 여기에 포함됩니다. README와 변경 로그 같은 패키지 메타데이터도 포함하세요. 단, 패키지의 license-file을 설정했다면 라이선스 파일은 따로 포함할 필요가 없습니다.
stack sdist를 실행하면 Stack이 경고를 출력합니다. 별로 유용하지 않은 것처럼 보이는 경고라도 모두 수정하세요. 예를 들어, Hackage의 카테고리는 그다지 유용하지 않지만 카테고리를 설정하지 않으면 Stack이 경고합니다.
다시 말해, 패키지 메타데이터와 실제 소스 파일을 분리하세요. 이렇게 하면 포매팅이나 코드 줄 수 세기처럼 모든 Haskell 파일에 대해 동작하는 스크립트를 쉽게 작성할 수 있습니다. 이름 자체는 중요하지 않지만, 구조는 대략 다음과 같으면 좋습니다:
library/YourPackage.hsexecutables/Main.hstests/Main.hs패키지 이름이 tasty-burrito라면 최상위 모듈은 TastyBurrito여야 합니다. Data.ByteString이나 Text.ParserCombinators.Parsec 같은 불필요한 모듈 계층은 피하세요. 패키지 이름은 kebab-case를 쓰지만, 모듈 이름은 CamelCase를 사용해야 합니다.
사용자는 import YourPackage만으로 시작할 수 있어야 합니다. 필요하다면 다른 패키지의 내용을 재수출(re-export)하세요. 정규화된(qualified) import를 염두에 두고 설계하고, singleton 같은 흔한 이름을 가져도 두려워하지 마세요.
사람들은 당신이 생각하지 못한 방식으로 패키지를 사용하고 싶어할 것입니다. 모든 것을 공개하되, 그것들이 반드시 공개된 API의 일부일 필요는 없습니다. 공개 범위를 신호하기 위해 Data.Text.Internal처럼 Internal 모듈 이름을 사용하세요.
GHC는 -Wall로 온갖 문제를 찾아냅니다. 오탐은 거의 없고 일반적으로 더 나은 코드를 작성하는 데 도움이 됩니다. 마음에 들지 않는 경고가 있다면 -Wno-warn-whatever로 비활성화하세요. 패키지를 개발할 때는 반드시 stack build --pedantic를 사용하세요. 그러면 경고를 꼭 수정하게 될 것입니다.
HLint는 코드 품질을 향상시키는 훌륭한 도구입니다. 하지만 따라갈 가치가 없는 제안도 있습니다. 예를 들어, 재수출 단축 제안은 Haddock 문서를 깨뜨립니다. 어떤 제안을 따를지는 스스로 판단하세요.
Brittany는 견고하고 커스터마이즈 가능한 Haskell 소스 코드 포매터입니다. 이를 사용하면 포매팅에 대해 더 이상 고민할 필요가 없습니다. 모양이 마음에 들지 않는다면 Brittany에서 고치면 되고, 그러면 모두의 포매팅이 좋아집니다. Brittany를 사용하면 PR에서 스타일 논쟁을 벌이는 무의미한 일을 피할 수 있습니다.
타입은 문서의 대체물이 아닙니다. 법칙(law)도 마찬가지입니다. 보통 함수는 특정 문제를 해결하기 위해 추가됩니다. 그 문제를 문서에서 예제로 보여 주세요.
Hspec은 사람이 읽기 쉬운 테스트를 작성하는 라이브러리입니다. Ruby의 RSpec에서 영감을 받았습니다. 속성 기반 테스트 작성을 위해 QuickCheck과 SmallCheck와도 통합됩니다.
Travis CI는 오픈 소스 프로젝트에 무료이며 GitHub와 통합됩니다. GitHub에 커밋을 푸시할 때마다 Travis CI가 테스트 스위트를 실행합니다. 이를 통해 패키지가 빌드 가능한 상태를 유지하기 쉬워집니다. 기본적으로 Travis CI는 Linux에서 실행되지만 macOS에서도 실행할 수 있습니다. Windows에서 테스트 스위트를 실행하려면 AppVeyor 사용을 고려하세요.
패키지가 실행 파일을 제공한다면, 라이브러리에서 정의하고 그것을 재수출하세요. 그러면 다른 패키지가 Haskell에서 그 실행 파일의 동작을 사용할 수 있습니다. 실행 파일은 다음과 같아야 합니다:
module Main (module YourPackage) where
import YourPackage (main)
패키지가 빠를 필요가 있다면 Criterion이 이를 측정하는 최고의 도구입니다. 반대로 패키지가 빠를 필요가 없다면, 벤치마크를 유지할 이유가 없습니다.
배포용 tarball을 수동으로 만들고 Hackage에 업로드하지 마세요. 대신 Travis CI가 하도록 하세요. Travis CI는 TRAVIS_TAG 환경 변수를 설정합니다. 이것이 설정되어 있다면 stack upload .를 실행해 패키지를 업로드할 수 있습니다. Travis CI에는 Hackage 자격 증명이 필요하므로, 빌드 로그에 이를 노출하지 않도록 주의하세요.