こんにちは!
私たちが最も得意とする言語は TypeScript です。TypeScript はフロントエンドの開発時はもちろんですがバックエンドの NodeJS へのビルドも可能なため、同じ言語をフロントエンドとバックエンドを扱えるため大変重宝しています。(言語が統一化され、エンジニアリソースの確保がしやすく、加えて社内エンジニアの育成もしやすい)
バックエンドの言語として時折 Python も採用することがありますが、機械学習や LLM 開発時などユースケースは限定的です。
本記事では、普段私たちが採用している内部向けのコーディングレギュレーションの一部を紹介します!参考にし、自社のレギュレーションに取り込んでみてください。
まず TypeScript はコーディング中及びビルド時に型エラーを検出し、デプロイ後のランタイムエラーの可能性を減らします。従来の JavaScript の実装では、結合試験時にバックエンドのランタイム上で存在しないプロパティを参照してエラーになったり、タイポでエラーになったりと、実行時にしか検出できない不具合が多々ありました。(単体テストでカバーするという思考もありますが)
TypeScript はコーディングを型安全(タイプセーフ)に行えるため、不具合軽減はもちろんのこと、エンジニアの実装中のストレス軽減にも繋がります。以下は少々極端な例ですが従来のコードと TypeScript の例です。
// JavaScript
const foo = {
hoge: 1
}
console.log(foo.hoge); // 1
console.log(foo.fuga); // undefined
console.log(huga.foo); // 実行時にエラー
console.log(foo.hoge.fuga); // 実行時にエラー
// TypeScript
type Foo = {
hoge: number
}
const foo:Foo = {
hoge: 1
}
console.log(foo.hoge); // 1
console.log(foo.fuga); // コーディング中にエラー検出可
console.log(huga.foo); // コーディング中にエラー検出可
console.log(foo.hoge.fuga); // コーディング中にエラー検出可
以下のようなユースケースで TypeScript を使用します。
遵守事項 | 補足 |
---|---|
utils/*.ts は 外部サービスでも利用できるレベルの汎用的機能をまとめる | 判断が難しい、または不明な場合は当社へ確認の上実装を行って下さい。 |
services/*.ts はプロダクトに依存した機能をまとめる | 判断が難しい、または不明な場合は当社へ確認の上実装を行って下さい。 |
ESLint の Warn 及び Error はすべて解消 | コミット前に ESLint を実行しフォーマットを実行して下さい。 |
OSS のパッケージの導入は事前承認性 | OSS パッケージ及び独自の製品の導入については当社へ承認を得てから導入して下さい。 |
タイプセーフ実装 | テストコードも TypeScript でタイプセーフに実装して下さい。 ただし、テストコードで意図して例外を発生させる必要がある場合は、タイプセーフな実装は問いません。 |
コミット作成前にローカルでのテスト疎通を確認 | テストコードがすでに実装されているプロジェクトの場合、テストが疎通しないコードをあげるのは控えましょう。 |
テストコードのカバレッジ計測 | カバレッジは指定がなければC1レベルを目指して実装してください。 |
理由 | 詳細 |
---|---|
予測可能性 | イミュータブルなオブジェクトは一度作成されるとその状態が変更されないため、プログラムの挙動を予測しやすくなります。 |
副作用の排除 | データが変更されると、それに依存する他の部分で予期せぬエラーが発生する可能性があります。しかし、イミュータブルなデータは変更が不可能なため、このような副作用を防ぐことができます。 |
デバッグの容易性 | データの変更が追跡しやすくなり、バグの発見と修正が容易になります。 |
総じて、バグの軽減や可読性の向上などメリットが非常に大きいです。 デメリットは通常の慣習的なプログラミングと比べて、実行効率がやや悪くなることが言えますが、現代のハードウェアの性能であればそこまで気にする必要はありません。イミュータブルであっても、慣習的な実装であっても速度に大差はありません。
関数型プログラミングは、イミュータブルなデータを前提としています。 関数型プログラミングは、データの変更を行わず、副作用のない関数(汎用的な関数)を使ってメソッドチェーンを使用しデータを変換することを目指します。
概念的な話のみで理解は難しいと思いますので、以下の実装例を記載します。
慣習的な実装
let totalScore = 0
const students = [
{ name: 'Alice', score: 50 },
{ name: 'Bob', score: 60 },
{ name: 'Charlie', score: 70 },
]
for (let i = 0; i < students.length; i++) {
totalScore += students[i].score
}
console.log(totalScore) // 180
イミュータブルな実装
import _ from 'lodash'
const students = [
{ name: 'Alice', score: 50 },
{ name: 'Bob', score: 60 },
{ name: 'Charlie', score: 70 },
]
const totalScore = _.sumBy(students, 'score')
console.log(totalScore) // 180
Lodashから提供されるその他の関数に目を通しておいて下さい。 以下は頻度多く使用する関数の例です。 特に chainを用いたメソッドチェーンを使用することで、変数を宣言しない関数型なプログラミングを実現できます。
let
や var
を使用しないようにしてください。 関数型とイミュータブル意識があれば、var
と let
使用せずにほとんどのケースを実装可能です。
悪い
const FooVar
function BarFunc() {}
const BarFunc = () => {}
良い
const fooVar
function barFunc() {}
const barFunc = () => {}
悪い
function BarFunc() {}
良い
const barFunc = () => {}
悪い
class foo {}
良い
class Foo {}
悪い
class Foo {
Bar: numberBaz() {}
}
良い
class Foo {
bar: numberbaz() {}
}
加えて、以下も遵守してください。
悪い
interface IFoo {}
interface foo {}
良い
interface Foo {}
悪い
interface Foo {
Bar: number
}
良い
interface Foo {
bar: number
}
悪い
type foo = {}
良い
type Foo = {}
悪い
type Foo = {
Bar: number
}
良い
type Foo = {
bar: number
}
悪い
enum color {}
良い
enum Color {}
悪い
enum Color {
red = 'red',
}
良い
enum Color {
Red = 'red',
}
悪い
const foo = { x: 123, y: undefined }
良い
const foo: { x: number; y?: number } = { x: 123 }
悪い
return null
良い
return undefined
悪い
const foo = ''
if (foo === '')
if (foo === undefined)
if (foo === null)
良い
import _ from 'lodash'
const foo = ''
if(_.isString(foo) && _.isEmpty(foo))
if(_.isUndefined(foo))
if(_.isNull(foo))
※ isEmpty
は数値の0を true 判定するため、注意してください。
Lodash
について詳しくはこちら
===
を使用してください。
悪い
if (foo == 0)
良い
if (foo === 0)
prettierを使用して、ファイル保存時及びコミット前にフォーマットが自動実行されます。 prettierの修正に従ってください。
type Foo = number | { someProperty: number }
interface Foo {
foo: string
}
interface FooBar extends Foo {
bar: string
}
class X implements FooBar {
foo: stringbar: string
}
フロントエンド開発でオブジェクト指向な実装はあまりしない想定しないため、迷ったらとりあえず type
を使ってください。 逆に共通処理の実装などを行う場合は、interface の使用を検討してください。
悪い
type Foo = {
bar: string
}
const foo = {} as Foo // 型エラーを検出できない
良い
type Foo = {
bar: string
}
const foo: Foo = {} // 型エラーを検出できる
本記事では私たちの TypeScript のレギュレーションを一部紹介しました。お好みでプロジェクトに合わせてカスタマイズしてみてください。
AWSサーバーレス、フロントエンドの開発はお気軽にお問い合わせください。