- 投稿日:
今の職場でコードを書いていてif
に対して原則else
を書かないという雰囲気があるのですが、個人的にはelse
を書きたいのでその話です。
世間的にelse
を書かないというコーディングルールは一定の支持があるようで、まぁ一種の宗教だとは思っていますが、個人的にはどうなんかなと思っています。まぁ割と最近の流行りな気もしていますが…。(昔はなかったと思う…というとアレですが)
else
があってほしい理由
else if
やelse
ブロックがあると処理の関連性が見やすくなると考えています。
処理の関連性がわかりやすくなる
例えば以下のコードの場合、if
が連続しているよりelse
がある方が処理の関連性が明示的に見えると思います。else
で繋げないことは基本的に処理として関連性がないはずです。
// ifのみ
export const hoge = (mode: 1|2|3) => {
if (mode === 1) {
// なんかの処理
return 'hoge';
}
if (mode === 2) {
// なんかの処理
return 'piyo';
}
if (mode === 3) {
// なんかの処理
return 'fuga';
}
}
// elseあり
export const piyo = (mode: 1|2|3) => {
if (mode === 1) {
// なんかの処理
return 'hoge';
} else if (mode === 2) {
// なんかの処理
return 'piyo';
} else if (mode === 3) {
// なんかの処理
return 'fuga';
}
}
予期せぬ不具合が入り込みづらくなる
if
だけで構成されたコードでは以下のようにif
ブロックの外に処理を入れ込むことができますが、このときA1, B1, C1のif
の外にある処理は後続処理に影響します。しかしこの書き方だと処理の影響範囲が見積もりづらく、不必要なバグを生む元になるため、個人的には避けたいと考えています。(他の処理の作用に引っ張られているので、副作用のある実装ということになると思っています)
またA1, B1, C1の行数が伸びるとコードの見通しが悪くなり、if
同士の関係性が分かりづらくなります。
else
を使うとこういった副作用的な振る舞いをする実装が作りづらくなり、影響範囲もブロックインデントで分かれるので、そういった心配がなくなります。
// ifのみ
export const hoge = (mode: 1|2|3) => {
// なんかの処理 A1
if (mode === 1) {
// なんかの処理 A2
return 'hoge';
}
// なんかの処理 B1
if (mode === 2) {
// なんかの処理 B2
return 'piyo';
}
// なんかの処理 C1
if (mode === 3) {
// なんかの処理 C2
return 'fuga';
}
}
else
が必要なのに書き忘れることが減る
else
を書かないことに捕らわれていると以下のようなコードが生まれることがあります。
さてこのコードにおいて!validHoge(hoge) || !hasPiyo
のケースはどのように処理されるのでしょうか?答えは処理されませんが、もしこのコードに対してUnit testが実装されておらず、特段のドキュメントもなければ、それが正しいのかどうかをコードから読み取ることが出来ません。
const isValidUsername = (username: string, hasInputed: boolean) => {
if (validUsername(username) && hasInputed) {
return requestSearchUsername(username);
}
}
せめてこう書いてあれば判断も付くでしょう。
const isValidUsername = (username: string, hasInputed: boolean) => {
if (validUsername(username) && hasInputed) {
return requestSearchUsername(username);
} else {
// 何もしない
}
}
因みにこの処理はユーザー登録フォームでユーザー名の書式が正しければ既に存在するユーザー名かどうかをAPIに問い合わせ、既に存在すればエラーメッセージを、存在しなければtrue
を返すという内容ですが、そもそも書式が不正である場合は何もしないため、ユーザーはエラーであることを知ることが出来ません。
もしelse
ブロックを書いていれば考慮漏れに気づけた可能性もあったのではないかと思います。
else
を使うケースを考えなくて良くなる
ifに対して原則else
を書かないというルールがあってもelse
を書かないと成り立たないケースは存在します。そう言った場合に原則if
しか書いてはならないというルールがあるとelse
を書いてよいかどうか考える必要が出てきますが、元からelse
も書いて良いルールであればそこは考える必要がありません。個人的に判断の余地が生じるコーディングルールはチーム開発ではない方が良いと考えています。
またこのルールの結果、本来必要だったelse
を書かなかったことにより不具合が発生する可能性もあります。(実装者が軽率にelse
を使わなかったことによる判断ミス)
そもそも何故else
を書いてはならないのでしょうか?コードのネストが深くなるからでしょうか?少なくともelse if
相当のif
には、その作用はありません。あるとしたら純粋なelse
ブロックはインデントが減るでしょう。しかしこのインデントはあったほうが処理の関連性が掴みやすくなると思います。
else
のないif
は悪か?
ここまで散々else
を書くべきと言ってきましたが、ではelse
がないif
は悪かと言うと、そうは思いません。例えば、以下のような早期リターンと呼ばれる記法であればそれは良いと考えています。では何を早期リターンとするかですが、個人的には例外的に処理を継続しない場合を一つの基準にしています。私もここの感覚は上手く言語化できていないのですが、恐らく何もしないときだけelse
を書かないのは問題ないと考えています。
これは本質的ではない処理のせいで、いたずらにネストが増えるのを防ぐためです。関連性がある場合にelse
を使うのがコードのメリハリとして目視で読んだときの認知性の向上に繋がると考えています。
export const putLog = (message: string) => {
if (message === '') return;
console.log(message);
}
この点については個人的に共感できる意見があったので以下の記事を紹介させて頂きます。
else句を使わないのが良いコードなの?いや、そんなはずは・・・ · DQNEO日記
記事中にある以下の部分、特殊ケースに対してガード節を使うというところで、例外ケースでのみ早期リターンをするという部分がありますが、やはりこれが一番無難だなと思いました。
条件記述の単純化 「ガード節による入れ子条件記述の置き換え」
特殊ケースに対してガード説を使う
ただ例外ケースとは何か?それは数値化でき、チーム開発で標準的に取り扱えるものなのか?と言われると、正直私もそこまでは言語化できていないので難しい部分ではあります。そもそも人間が書くコードが完全に均一になることはないと考えているので、そこまで強い思想を持って考えてはいないです。(もしChatGPTに書かせてもバラツキは出るでしょう)
私がこの記事で言いたいのは単にelse
を書いてもいいのではないかということと、その場合でも早期リターンは許容しても問題ないだろうというところなので、一旦早期リターンの基準についてはここでは考えないこととします。
そうなると結局else
なしでif
だけあればいいじゃないかという話に戻ってくるとは思うのですが、正直そこは常識で考えてほしいというところです。まぁ常識も人の数だけあるので難しいですし、そんな物があればこんな記事も生まれていないわけで、ソフトウェア開発は難しいですね。
- 投稿日:
Groovy Scriptの読み方が分からなかったので読み解き方のメモ。全て憶測
確認環境
Env | Ver |
---|---|
Jenkins | 2.249.1 |
Groovy Script | 不明 |
サンプルコード
def credentials = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
jenkins.model.Jenkins.instance.getItemByFullName("ここにフォルダパス")
)
def cred = credentials.findResult { it.id == "ここに取得したいやつのCredentials ID" ? it : null }
読み解き方
個人的な解釈なので特に根拠はない。全て憶測。
lookupCredentialsの部分
端的に言うとデータ型とストア名のようなものを指定し、認証情報を取得する機能であると思われる。Groovy Script的にはCollectionが返ってくる。
- com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials())
- 第一引数にデータ型、第二引数にLOOKUP条件を設定するものと思われる
- org.jenkinsci.plugins.plaincredentials.StringCredentials.class
- 文字列の認証情報ということだと思われる
- jenkins.model.Jenkins.instance.getItemByFullName())
- 現在のJenkinsインスタンス(Jenkinsシステムそのもの)から名前で要素を取得するものだと思われる
findResultの部分
- http://docs.groovy-lang.org/docs/groovy-2.1.3/html/groovy-jdk/java/util/Collection.html#findResult(groovy.lang.Closure)
- 恐らくJSのArray.findと似たような機能で、
it
の中に配列要素が入ってくるので、それを使って配列内容の要素を取り出すのに使うのだと思う
- 恐らくJSのArray.findと似たような機能で、
- 投稿日:
undefinedの判定方法が複数あるということでundefined判定の処理速度比較をしてみたのでその結果。
端的に言うと、hoge === undefined
とtypeof hoge === 'undefined'
の二方式がある。後者は原則考慮不要だが、言語仕様上存在しているので比較したが、現実的に見た場合、どちらで記述した場合でも処理速度に有意な差はないように感じた。
確認環境
Env | Ver |
---|---|
Node.js | 20.1.0 |
TypeScript | 4.9.5 |
@swc/core | 1.3.8 |
比較結果
hoge === undefined
の方が早く見えるが実行するタイミングで変わるので誤差の範疇だと思う。
方式 | ms |
---|---|
hoge === undefined |
4,514 |
typeof hoge === 'undefined' |
4,515 |
確認コード
const tyof = (param?: string) => {
return typeof param === 'undefined';
};
const undef = (param?: string) => {
return param === undefined;
};
const tyStart = +new Date();
for (let i = 0; i < 10000000000; i++) {
tyof();
}
console.log('typeof', +new Date() - tyStart);
const unStart = +new Date();
for (let i = 0; i < 10000000000; i++) {
undef();
}
console.log('undefined', +new Date() - unStart);
TSから生成されたJS
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
const tyof = (param)=>{
return typeof param === 'undefined';
};
const undef = (param)=>{
return param === undefined;
};
const tyStart = +new Date();
for(let i = 0; i < 10000000000; i++){
tyof();
}
console.log('typeof', +new Date() - tyStart);
const unStart = +new Date();
for(let i = 0; i < 10000000000; i++){
undef();
}
console.log('undefined', +new Date() - unStart);
あとがき
MDNを読む限りtypeof hoge === 'undefined'
は該当変数が存在しない場合に有用なようであるが、TypeScriptで書いている場合、通常このようなコードが生まれることがなく、仮に起きるとした場合、次のようなコードになるため現実的に考慮する必要はない。なおMDNにも「こんなことはしないこと」と書いてあるので、一般的なコードでないことは客観的にも伺えるだろう。
(() => {
const undefined = 123;
const hoge = undefined;
if (typeof hoge === 'undefined') {
console.log('hoge is undefined');
} else {
console.log('hoge is not undefined');
}
})();
上記コードの実行結果としてはhoge is not undefined
が出力される。
このコードの主な問題点
const undefined = 123;
というコードは予約語を変数名にしているため、混乱を招くコードであり、書かないことが好ましい- MDNには予約語ではないとあるが、一般的には予約語の一つとして解釈して支障ないと考える
- このコードはESLintのeslint:recommendedで検知されるため、通常であれば書かれることはない
なお、このコードは例示のために即時実行関数形式で記述しているが、必要がない限りこの形式での実装は避けたほうが問題が少なくなると思う。これは不必要なネストが生まれたり、スコープの混乱を生むためである。
- 投稿日:
HuskyとはNode.jsを利用した開発で非常によく使われているGit hooksのユーティリティだが、個人的にはこのツールの存在価値に疑問を感じている。
という訳で、この記事ではHuskyのメリデメを考えた結果と、Huskyが何をしているか、Huskyの必要性について書いてゆく。
Huskyがあるメリット
Huskyは極めて有名なツールであるため、Huskyが入っているとこのプロジェクトはGit hooksで管理されており、標準化されていることが確認できるだろう。恐らくHuskyのメリットはこれ以外に存在しないと考えている。
Huskyがあるデメリット
Huskyの管理をしないといけない、Huskyも地味にアップデートするからだ。これは明確なコストである。
そしてHuskyのコードやリポジトリを見たことがある人は多分ほとんどいないと思う。更に言えばHuskyが何をしているかすらも知らない人だっているはずだ。そんな得体の知れないものを使うのは怖いというところだ。
Huskyは何をしているか?
端的に言えばGit hooksのパスを .husky/
に設定しているだけである。
要するに git config core.hooksPath .husky
を叩いているだけだ。
もう少し細かく言えば以下に相当する処理を実行している。
mkdir -p .husky/_
cp husky.sh .husky/_
git config core.hooksPath .husky
勿論、ソースコードには他の処理も書かれているのだが、実質的には上記三行が全てと言って良い。
husky.sh
を活用しているケースがどれほどあるか怪しいことを考えると、本質は git config core.hooksPath .husky
だと思うので、正直あるだけ邪魔では?と考えている。
Huskyの必要性
ここまででHuskyがしていることは git config core.hooksPath .husky
だということが解ったが、だとしたらHuskyは本当に必要なのだろうか?私は特に理由がないのであれば package.json
で husky install
と書いてあるところに git config core.hooksPath .githooks
とでも書いておけば良いのではないか?と思っている。恐らく何も不都合はないはずだ。
ただ世の中には色々な事情があり、使わざるを得ないケースもあると思う。しかし、可能であれば排除してもいいのではないか?個人的にはそう思っている。
何故この記事を書いたか
「この世からHuskyを滅ぼすため」というのはまぁ冗談だが、個人的にHuskyの存在価値があまり良くわかっておらず、多分世間の人もあまり理解できていないと勝手に考えていて、可能であればプロジェクトに入れたくないと考えているので、そのお気持ち表明というか、そんな感じだ。
ここからは余談だが、Huskyには結構な数のスポンサーが付いていて、恐らく毎月それなりの収入があると思われる。以下はHuskyのスポンサーである。
個人的にHuskyは最も成功したOSSの一つではないかと考えている。理由としてHusky自体は非常に単純なプロダクトであり、コミット履歴を見てもさしたるメンテナンスがされておらず、ほぼ手放しで維持されていると思われるからだ。
しかし、Huskyはそれなりの額の寄付を集めており、この記事を書いた時点で確認できるだけでも最低 10USD * (4 + 16) + 100USD * (4 + 2)
の寄付がされており、つまり800USD、日本円にして11.2万円ほどだ。何もしてないのに毎月この収入があるのは大分ありがたいだろう。他のOSSならIssueやPull Requestsに対して対応したり、コード本体のメンテナンスがあるはずだが、Huskyにそんなものはないため、プロダクトの維持コストに対して非常によく寄付を集められていると感じる。