Quantcast
Browsing Latest Articles All 57 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

JavaScript ベスト・オブ・ザ・イヤー 2019

GitHubで最もホットなライブラリをランキング表示してくれるbestofjs.orgというサイトがあります。
そしてここは毎年、その一年で最もホットだったライブラリを発表しています。

選考基準は『その年増えたスターの数』であり、恣意的要素の入る余地はありません。
ちなみに2016年の総合ランキング1位はVue.js、2017年の総合ランキング1位はVue.js、2018年の総合ランキング1位はVue.jsです。

以下は2019年のランキング、2019 JavaScript Rising Starsの日本語訳です。

JavaScript ライジングスター 2019

4回目のJavaScript ライジングスターにようこそ!

2019年にGitHubで最も注目を集めたプロジェクトを、数字で見てみましょう。

以下のチャートは、2019年の一年間にGitHubで増加したスターの数を比較したものです。Webプラットフォームに関するベストプロジェクトを集めたリストであるBest of JavaScriptからの分析となります。各プロジェクトをクリックすると、プロジェクトの詳細を閲覧することができます。

総合ランキング

1位: Vue.js
2位: VS Code
3位: React
4位: Vue Element Admin
5位: Svelte
6位: Axios
7位: Ant Design
8位: TypeScript
9位: Puppeteer
10位: Create React App

4年連続でVue.jsが総合優勝を飾り、2019年にも30000以上のスターを稼ぎ出しました。

次いでReactVSCodeが、昨年と同じように続いています。

総合ランキングで最大の伸びを示したのがVue Element Adminでした。
これは、Vue.jsコンポーネントを使った優れたダッシュボードを構築することができるソリューションで、堂々の4位を取得しました。

Svelteは数年前から存在するプロジェクトですが、2019年ついに飛翔し5位に入りました。

TypeScriptがベストテン入りし、この成功は過去数年のJavaScriptの変化の集大成のひとつです。

Node.jsの作者が新たに作ったJavaScriptランタイムDenoは、2018年に新登場しました。
これは13位で、まだトレンドに入っています。

フロントエンド フレームワーク

1位: Vue.js +31.4k
2位: React +22.9k
3位: Svelte +20.0k
4位: Angular +12.0k
5位: Omi +3.8k

2019年はSvelteが躍進してAngularを追い抜き、
Vue.jsReactの背後を突く3位に上昇しました。

これはBIG-3がBIG-4になったことを意味するのでしょうか?

Svelteは他のフレームワークと大きく異なっていて、実際にはフレームワークではなく、ビルド時に全てを構築するコンパイラのようなものです。

ReactやVue同様に、データが変更されたときに画面を更新するアプリを開発者は書くことができます。
ただし最大の違いとして、そのフレームワークがブラウザ上で実行されるのではなく、ビルド時に実行されます。
コンパイラは、開発者が作成したコンポーネントを、DOMを直接操作する命令型コードに変換します。
そのため、ブラウザに渡されるコードは小さくなり、非常に高速に実行されます。
たとえば組み込みデバイス向けのアプリケーションなどに適しているでしょう。

そのパフォーマンスに加えて、ややこしいCSSトランジションも簡単に制御することができます。

バージョン3ではリアクティブな仕組みも強化されています。
UIをトリガーするだけで、変数までも更新してくれます。

SvelteがこれまでのBIG-3と同じくらい成功すると断定するには時期尚早ですが、2020年はこれをフォローしておく価値があるでしょう。

Node.js フレームワーク

1位: Nest +11.5k
2位: Next.js +10.6k
3位: Strapi +10.2k
4位: Nuxt +7.4k
5位: Express +5.1k

Node.jsに新たなチャンピオン、Nestが誕生しました。
NestはAngularから多くの概念を取り入れた、フルスタックのフレームワークです。

2018年の勝者であったNext.jsは、その席こそ譲ったものの堂々の2位です。
フロントエンドをReactで、バックエンドをNode.jsで構築する際に最適なソリューションのひとつです。

3位に入ったStrapiはオープンソースのヘッドレスCMSで、APIエンドポイントを迅速に生成・管理することができます。
『4コマンドで始めよう』
APIエンドポイントはRESTもしくはGraphQLに対応しています。

4位はNuxtで、フロントエンドにVue.jsを使うときに最適なフルスタックフレームワークです。
サーバサイドレンダリング、クライアントサイド、あるいは静的のみといった様々な戦略に対応しています。

ExpressはNode.jsのための古びたフレームワークですが、まだトップ5に残っています。
巨大なエコシステムが存在し、多くのNode.js開発者には馴染みがあることでしょうが、最後のコミットから既に半年以上が経過していることに注意が必要です。
時代は変わっていますか?

React エコシステム

1位: Ant Design +14.6k
2位: Create React App +13.5k
3位: Gatsby +11.5k
4位: Next.js +10.6k
5位: Material UI +10.2k

1位にAnt Design、5位にMaterial UIと、React用のコンポーネントとヴィジェットが入賞しました。

2位のCreate React Appは、Reactアプリを新しく始める際の、事実上のデファクトスタンダードです。
今年の目玉として、TypeScriptがサポートされました。

今年のReact世界で最大の変更点は、フックの導入です。

Reactはビューレイヤだけしか提供しないので、コンポーネント間でロジックを共有する方法は常に議論の的になっています。
その進化は概ね4ステップに分けられます。
・2013年:Mixin
・2015年:高階コンポーネント
・2017年:Render Propsパターン
・2019年:Hooks

今年は、フックの力を使うことで問題を解決したライブラリが多数現れました。
Redux
React Router
React Hook Form

Hooksで進化の最終段階に到達しましたか?

Vue エコシステム

1位: Vue Element Admin +22.7k
2位: Element +9.4k
3位: Vuetify +7.5k
4位: Nuxt +7.4k
5位: vue-cli +5.8k

2連覇したVue Element Adminは、Elementを使って美しいダッシュボードを構築するVue.jsコンポーネントです。

2位のElementは、多くのコンポーネントを持つUIライブラリです。

マテリアルコンポーネントフレームワークであるVuetifyは、7月にバージョン2がリリースされ急上昇してランクインしました。

NuxtはVue.jsアプリケーションを構築するWebフレームワークであり、3年連続でランクインしています。

5位には、強力なGUIを備えプロジェクトの立ち上げを加速するオフィシャルのツールキット、vue-cliが入りました。

Angular エコシステム

1位:ngx-admin +4.8k
2位:Material Design for Angular +2.4k
3位:Angular CLI +2.1k
4位:NG-ZORRO +1.7k
5位:NgRx +1.3k

Angularプロジェクトで最も人気であるngx-adminは、管理ダッシュボードを構築するためのテンプレートを提供します。

2位のMaterial Design for AngularはAngular CDKと似たような、マテリアルデザインのAngular公式コンポーネントです。

ReactやVueと同様に、デザインコンポーネントであるNG-ZORROが4位に入りました。

Angularは2019年5月にバージョン8のメジャーリリースが行われました。
新機能のひとつとして、Angular CLIはモダン向けとレガシー向けにバンドルを分けることによってバンドルサイズを20%減少させることに成功しました。
また既存のCLIコマンドを変更したり新たなコマンド追加したりできる、新たなBuilders APIも存在します。

Angularのバージョン9もRCとなっていて、2020年の前半にリリースされる見込みです。
バージョン9の主な変更点はIvyと呼ばれる新しいコンパイラで、構築時間の短縮及びバンドルサイズの減少が期待されています。

テスト

1位:Puppeteer +13.9k
2位:Cypress +7.8k
3位:Jest +6.5k
4位:React Testing Library +5.7k
5位:Puppeteer Recorder +2.2k

今年は、カテゴリをテストフレームワークに限定するのではなく、E2Eテストやブラウザ自動化ツールなども対象としました。

Puppeteerは非常に人気のあるヘッドレスChrome制御ツールです。
Webスクレイピングやスナップショット撮影など多くのユースケースがあります。
ChromeエクステンションのPuppeteer Recorderも5位にランクインしています。

CypressはE2Eテストを行うためのソリューションであり、ユーザ操作とWebページの関連を記述する優れたUIを提供します。

3位のJestは、フロントエンドとバックエンドの両方のプロジェクトで用いられる、最も人気のあるテストフレームワークです。

React Testing Libraryは、Kent C. DoddsによるDOM Testing LibraryをラップしたReactアプリのテストソリューションです。
実装の詳細をテストするのではなく、アプリの動作をテストするのに役立つパターンと抽象化を提供します。

モバイル

1位:React Native +11.3k
2位:Quasar +5.0k
3位:Ionic +3.6k
4位:Expo +3.5k
5位:cube-ui +2.6k

コンパイラ

1位:TypeScript +13.9k
2位:Babel +4.2k
3位:Flow +1.7k
4位:Reason +1.3k
5位:Sucrase +1.1k

ビルドツール

1位:Webpack +6.3k
2位:Parcel +5.4k
3位:Rollup +2.7k
4位:Microbundle +1.3k
5位:Gulp +841

CSS in JavaScript

1位:Styled Components +6.0k
2位:Emotion +3.3k
3位:Linaria +2.8k
4位:styled-system +2.4k
5位:CSS Modules +1.8k

GraphQL

1位:Gatsby +11.5k
2位:Hasura GraphQL Engine +8.1k
3位:Prisma +4.5k
4位:Gridsome +3.8k
5位:Apollo client +3.4k

学習リソース

1位:You Don't Know JS +20.1k
2位:30 seconds of code +18.1k
3位:JS Algorithms & Data Structures +17.8k
4位:Node.js Best Practices +16.1k
5位:Tech Interview Handbook +13.0k

まとめ

フロントエンドのトレンドは興味深い傾向を示しています。
Svelteは今後も成長し続けるでしょうか?

あるいは、WebコンポーネントなどWeb標準の採用が増えるでしょうか?

2019年の最も興味深い話のひとつは、AppleがWebコンポーネントを使用する音楽アプリをリリースしたという事実であり、UIフレームワークのひとつであるStencilでコンパイルされています。
これはWebコンポーネントが実アプリに使用された例です。

ネイティブモジュールを使用するアプリをリリースできるという事実は、大きな改善です。

バックエンドでは、Node.jsは登場から10年経ちましたが、今でも新しい機能が精力的に追加され続けています。
v13.2.0の時点で、Node.jsはESモジュールをそのままサポートし、WebAssemblyモジュールをインポートできます。
Workers Threads APIを使えば重いプログラムも実行することができます。

ブラウザにおいてもNode.jsにおいてもプラットフォームは進化し続けており、これがJavaScriptの開発を非常にエキサイティングなものにしてくれます。
ご清聴ありがとうございました。
また来年お会いしましょう!

感想

現在のスター数のランキングではなく、2019年の一年間で増加したスター数のランキングです。
累計は多いけど落ち目の技術はランクに入らないため、最近勢いのある技術がわかります。

総合ランキングは今年もVue.jsの勝利で、堂々の4連覇です。
これから使う技術は、基本的にランキング上位の中から選択しておけば問題ありません。
目的があるのならばそれに適した技術を選ぶべきですが、よくわからないけどなんとなくやってみたい、という段階であれば迷わずVue.jsを選びましょう。
プログラミングは数こそが力なので、わざわざマイナーなものを使ったところでいいことは何もありません。

ところで、昨年までは全てのジャンルに解説があったのですが、今回はランキングだけで解説のないジャンルが目立ちます。
飽きたのでしょうか?

2016年から2019年までのJavaScriptの全て

以下はAlbertoM( Webサイト / Twitter / GitHub / dev.to )による記事、Everything you need to know from ES2016 to ES2019の日本語訳です。

Everything you need to know from ES2016 to ES2019

JavaScriptは絶え間なく進化し続けている言語であり、この数年で多くの新機能がECMAScriptの仕様に追加されました。

この記事は私の著書Complete Guide to Modern JavaScriptからの抜粋であり、そしてこの本はES2016・ES2017・ES2018・ES2019の新しい機能について解説しています。

記事の最後に、全てを要約したチートシートを用意しています。

Everything new in ES2016

ES2016で追加された機能はわずか二つです。
・Array.prototype.includes()
・指数演算子

Array.prototype.includes()

includes()メソッドは、配列に特定の値が含まれる場合はtrueを返し、含まれていなければfalseを返します。

letarray=[1,2,4,5];array.includes(2);// truearray.includes(3);// false

Combine includes() with fromIndex

検索を開始するインデックスをincludes()に渡すことができます。
デフォルトは0で、負の値を渡すこともできます。
最初の値は検索する要素で、2番目の値は開始インデックスです。

letarray=[1,3,5,7,9,11];array.includes(3,1);// true 1番目つまり最初から検索開始するarray.includes(5,4);// falsearray.includes(1,-1);// false 最後尾から検索開始するarray.includes(11,-3);// true

array.includes(5,4);はfalseを返します。
4番目の値から検索を開始するため、2番目にある値5は検索範囲外だからです。

array.includes(1,-1);はfalseを返します。
インデックス-1は配列の最後の要素という意味で、そこから検索を開始するためです。

array.includes(11,-3);はtrueを返します。
インデックス-3は配列の最後から3番目の要素という意味で、そこから検索を始めたため範囲内に11を発見しました。

The exponential operator

ES2016より前は、以下のように書いていました。

Math.pow(2,2);// 4Math.pow(2,3);// 8

指数演算子の導入により、以下のように書けるようになりました。

2**2;// 42**3;// 8

複数の演算を組み合わせる際に特に役立ちます。

2**2**2;// 16Math.pow(Math.pow(2,2),2);// 16

Math.pow()を使うと長く面倒な書式になります。
指数演算子を使うと、同じことをより早くよりクリーンに記述することができます。

ES2017 string padding, Object.entries(), Object.values() and more

ES2017では、多くのクールな新機能が追加されました。
それらを以下に解説していきます。

String padding (.padStart() and .padEnd())

文字列の先頭もしくは末尾にパディングを付けられます。

"hello".padStart(6);// " hello""hello".padEnd(6);// "hello "

パディング値に6を指定したのに、スペースが1しか入らなかったのはなぜでしょう。
padStartとpadEndは規定文字数になるように穴埋めするからです。
上記例の場合、"hello"は5文字あるため、不足した1文字だけがパディングされました。

"hi".padStart(10);// "        hi" 8文字埋められた"welcome".padStart(10);// "   welcome" 4文字埋められた

Right align with padStart

値を右揃えしたいときにもpadStartが使えます。

conststrings=["short","medium length","very long string"];constlongestString=strings.sort(str=>str.length).map(str=>str.length)[0];strings.forEach(str=>console.log(str.padStart(longestString)));// very long string//    medium length//            short

まず、最も長い文字列の長さを取得します。
次いですべての文字列にpadStartを適用し、文字列長を最も長い文字列に合わせました。

Add a custom value to the padding

スペースだけではなく、任意の文字でパディングすることができます。

"hello".padEnd(13," Alberto");// "hello Alberto""1".padStart(3,0);// "001""99".padStart(3,0);// "099"

Object.entries() and Object.values()

とりあえずObjectを作成します。

constfamily={father:"Jonathan Kent",mother:"Martha Kent",son:"Clark Kent",}

かつてのJavaScriptは、プロパティに以下のようにアクセスしていました。

Object.keys(family);// ["father", "mother", "son"]family.father;// "Jonathan Kent"

Object.keys()は、プロパティのキーのみを返します。

ES2017では、プロパティにアクセスする方法が2種類増えました。

Object.values(family);// ["Jonathan Kent", "Martha Kent", "Clark Kent"]Object.entries(family);// ["father", "Jonathan Kent"]// ["mother", "Martha Kent"]// ["son", "Clark Kent"]

Object.values()はプロパティ値のみの配列を返し、Object.entries()はキーと値の両方を含む配列の配列を返します。

Object.getOwnPropertyDescriptors()

このメソッドは、オブジェクトの持つ全てのプロパティディスクリプタを返します。
ディスクリプタの属性はvalue・writable・get・set・configurable・enumerableです。

constmyObj={name:"Alberto",age:25,greet(){console.log("hello");},}Object.getOwnPropertyDescriptors(myObj);// age:{value: 25, writable: true, enumerable: true, configurable: true}// greet:{value: ƒ, writable: true, enumerable: true, configurable: true}// name:{value: "Alberto", writable: true, enumerable: true, configurable: true}

Trailing commas in function parameter lists and calls

これは、本当に小さな変更です。
しかしこれで、そのパラメータが最後であるかどうかをいちいち気にせずに末尾カンマを書けるようになりました。

// 昔constobject={prop1:"prop",prop2:"propop"}// 現在constobject={prop1:"prop",prop2:"propop",}

2番目のプロパティは最後にカンマが増えていることに注目してください。
入れなくてもエラーにはなりませんが、同僚やチームメイトの生活を楽にするためにも入れておくことをお勧めします。

// カンマ入れないconstobject={prop1:"prop",prop2:"propop"}// 同僚が最終行をコピペしてプロパティを追加したconstobject={prop1:"prop",prop2:"propop"prop3:"propopop"}// 突然エラーが出るようになった

Shared memory and Atomics

MDNによると (日本語版)

メモリーが共有されている場合、複数のスレッドがメモリー内の同じデータを読み書きできます。アトミック演算では、予測される値の書き込みと読み込みを保証するため、次の演算が開始される前に現在の演算が完了し、その演算が割り込まれないようにします。

Atomicはコンストラクタではありません。
プロパティとメソッドは全て静的であり、newしたりinvokeしたり関数として呼び出したりすることはできません。

Atomicが持つメソッドは以下のようなものがあります。
・ add / sub
・ and / or / xor
・ load / store

Atomicは、汎用固定長バイナリデータバッファSharedArrayBufferなどで使用されます。
いくつかの例を見てみましょう。

Atomics.add(), Atomics.sub(), Atomics.load() and Atomics.store()

// SharedArrayBuffer作成constbuffer=newSharedArrayBuffer(16);constuint8=newUint8Array(buffer);// これに各計算するuint8[0]=10;console.log(Atomics.add(uint8,0,5));// 10console.log(uint8[0])// 15console.log(Atomics.load(uint8,0));// 15

最初のAtomics.add()は加算を行いますが、返り値は計算する前の値です。
その後uint8[0]を参照すると、addが実行されたあとなので値が15になっていることが確認できます。

配列値をAtomicに取得するにはAtomics.load()を使い、第一引数が対象の配列、第二引数がインデックスです。

Atomics.sub()Atomics.add()と同じ挙動で、減算を行います。

// SharedArrayBuffer作成constbuffer=newSharedArrayBuffer(16);constuint8=newUint8Array(buffer);// これに各計算するuint8[0]=10;console.log(Atomics.sub(uint8,0,5));// 10console.log(uint8[0])// 5console.log(Atomics.store(uint8,0,3));// 3console.log(Atomics.load(uint8,0));// 3

Atomics.sub()を使って10から5を引きました。
計算自体の返り値は、Atomics.add()と同じく計算する前の値であり、すなわち10です。

次にAtomics.store()を使い特定の値、今回は配列の0番目のインデックスに3を登録しました。

Atomics.store()は渡した値をそのまま返します。
Atomics.load()すると、値は書き替えられた後なので5ではなく3になります。

Atomics.and(), Atomics.or() and Atomics.xor()

これらはそれぞれAND、OR、XORのビット演算を行います。
ビット演算の詳細はWikipediaなどで読むことができます。

ES2017 Async and Await

ES2017では、async/awaitと呼ばれる新たなPromiseが導入されました。

Promise review

その前に、まず普通のPromise構文を簡単に復習しましょう。

// GitHubからユーザを取得fetch('api.github.com/user/AlbertoMontalesi').then(res=>{// 値をJSONで返すreturnres.json();}).then(res=>{// 全てが正常に動作したらここに来るconsole.log(res);}).catch(err=>{// エラーがあったらここに来るconsole.log(err);})

GitHubからユーザを取得してコンソールに出力するだけの簡単な例です。

また別の例を見てみましょう。

functionwalk(amount){returnnewPromise((resolve,reject)=>{if(amount<500){reject("the value is too small");}setTimeout(()=>resolve(`you walked for ${amount}ms`),amount);});}walk(1000).then(res=>{console.log(res);returnwalk(500);}).then(res=>{console.log(res);returnwalk(700);}).then(res=>{console.log(res);returnwalk(800);}).then(res=>{console.log(res);returnwalk(100);}).then(res=>{console.log(res);returnwalk(400);}).then(res=>{console.log(res);returnwalk(600);});// you walked for 1000ms// you walked for 500ms// you walked for 700ms// you walked for 800ms// uncaught exception: the value is too small

Async and Await

これをasync/awaitで書き換えるとこうなります。

functionwalk(amount){returnnewPromise((resolve,reject)=>{if(amount<500){reject("the value is too small");}setTimeout(()=>resolve(`you walked for ${amount}ms`),amount);});}// asyncなfunctionを作成asyncfunctiongo(){// awaitがあれば終わるまで待つconstres=awaitwalk(500);console.log(res);constres2=awaitwalk(900);console.log(res2);constres3=awaitwalk(600);console.log(res3);constres4=awaitwalk(700);console.log(res4);constres5=awaitwalk(400);console.log(res5);console.log("finished");}go();// you walked for 500ms // you walked for 900ms // you walked for 600ms // you walked for 700ms // uncaught exception: the value is too small

非同期関数を作成するには、まずasyncキーワードを記載します。
このキーワードが入った関数はPromiseを返すようになります。
Promiseでない値を返そうとした場合、自動的にPromiseでラップされて返されます。
awaitキーワードは、async関数内でのみ機能します。
awaitを書くと、プログラムはPromiseが結果を返すまでそこで停止します。

async関数の外でawaitキーワードを使うとどうなるでしょうか。

// asyncではない関数functionfunc(){letpromise=Promise.resolve(1);letresult=awaitpromise;}func();// SyntaxError: await is only valid in async functions and async generators// トップレベルletresponse=Promise.resolve("hi");letresult=awaitresponse;// SyntaxError: await is only valid in async functions and async generators

復習:awaitは、async関数内でのみ使用可能。

Error handling

Promiseではエラーを.catch()でキャッチします。
これについては特に違いはありません。

asyncfunctionasyncFunc(){try{letresponse=awaitfetch('http:your-url');}catch(err){console.log(err);}}asyncFunc();// TypeError: failed to fetch

関数内でエラーハンドリングしていない場合は、以下のように書くこともできます。

asyncfunctionasyncFunc(){letresponse=awaitfetch('http:your-url');}asyncFunc();// Uncaught (in promise) TypeError: Failed to fetchasyncFunc().catch(console.log);// TypeError: Failed to fetch

ES2018 Async Iteration and more?

ES2018で導入された機能も見ていきましょう。

Rest / Spread for Objects

ES6 (ES2015) でスプレッド構文が導入されたことを覚えていますか?

constveggie=["tomato","cucumber","beans"];constmeat=["pork","beef","chicken"];constmenu=[...veggie,"pasta",...meat];console.log(menu);// Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]

スプレッド構文にRestパラメータがオブジェクトに対しても使用可能になりました。

letmyObj={a:1,b:3,c:5,d:8,}// zは残り全部let{a,b,...z}=myObj;console.log(a);// 1console.log(b);// 3console.log(z);// {c: 5, d: 8}// スプレッド構文でクローンletclone={...myObj};console.log(clone);// {a: 1, b: 3, c: 5, d: 8}myObj.e=15;console.log(clone)// {a: 1, b: 3, c: 5, d: 8}console.log(myObj)// {a: 1, b: 3, c: 5, d: 8, e: 15}

スプレッド構文を使うとオブジェクトのクローンが簡単に作成できます。
元のオブジェクトを変更しても、クローンしたオブジェクトは変更されません。

Asynchronous Iteration

非同期イテレータを用いて、データを非同期的に反復することができます。

ドキュメントによると、

非同期イテレータは、next()メソッドが{ value, done }のペアを返すこと以外、イテレータにそっくりです。

従って、for-await-ofループで反復処理してPromiseにすることができます。

constiterables=[1,2,3];asyncfunctiontest(){forawait(constvalueofiterables){console.log(value);}}test();// 1// 2// 3

ループの実行中、[Symbol.asyncIterator]()を用いてデータソースから非同期イテレータを作成します。
次のループにアクセスするたびに、返ってきたPromiseを暗黙的にawaitします。

Promise.prototype.finally()

Promiseが終了したときに呼び出されます。

constmyPromise=newPromise((resolve,reject)=>{resolve();})myPromise.then(()=>{console.log('still working');}).catch(()=>{console.log('there was an error');}).finally(()=>{console.log('Done!');})

.finally()もPromiseを返すので、さらにthencatchを続けることも可能ですが、そこに渡ってくるPromiseはfinallyではなく元の値です。

constmyPromise=newPromise((resolve,reject)=>{resolve();})myPromise.then(()=>{console.log('still working');return'still working';}).finally(()=>{console.log('Done!');return'Done!';}).then(res=>{console.log(res);// still working})

見てのとおり、最後のthenに渡ってくるPromiseは、finallyによるものではなく最初のthenが作ったものです。

RegExp features

正規表現の機能が4種類追加されました。

s(dotAll) flag for regular expressions

sフラグが導入されました。
これにより、'.'が改行を含む任意の1文字に一致するようになります。

/foo.bar/s.test('foo\nbar');// true

RegExp named capture groups

ドキュメントによると、

番号付きキャプチャグループを使用して、正規表現が一致した文字列の特定の個所を参照することができます。
キャプチャグループには一意の番号が割り当てられており、その番号で参照することができますが、これにより正規表現の把握とリファクタリングが難しくなります。
日付に一致する/(\d{4})-(\d{2})-(\d{2})/を例に取ると、どの番号が月に対応し、どの番号が日に対応しているかは、コードをよく調べてみないと理解できません。
また月と日の順番を入れ替えたいとなったら、参照する番号の方まで書き換えなければなりません。
ここでキャプチャグループに(?<name>...)構文を用いて、識別子nameで参照することができるようになります。
日付の正規表現を/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/uと書けます。
各識別子は一意であり、ECMAScriptの命名規則に従う必要があります。
名前付きキャプチャグループには、返り値のgroupsプロパティからアクセスすることができます。
以下の例のように、名前付きキャプチャグループと同時に、番号付きの参照も作成されます。

letre=/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;letresult=re.exec('2015-01-02');// result.groups.year === '2015';// result.groups.month === '01';// result.groups.day === '02';// result[0] === '2015-01-02';// result[1] === '2015';// result[2] === '01';// result[3] === '02';let{groups:{one,two}}=/^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');console.log(`one: ${one}, two: ${two}`);// one: foo, two: bar

RegExp Lookbehind Assertions

ドキュメントによると、

後読みアサーションを使用すると、手前に別のパターンが存在するパターンにマッチすることができます。
たとえば$記号を含まずに金額だけマッチするような使い方ができます。
肯定後読みアサーションは(?<=...)と記述します。
$記号を含まずに金額だけマッチさせたい場合は/(?<=$)\d+(\.\d*)?/とし、これは$10.53にマッチして10.53がキャプチャされます。
しかし€10.53にはマッチしません。
否定後読みアサーションは(?<!...)と記述し、手前に別のパターンが存在しないパターンにマッチします。
/(?<!$)\d+(?:\.\d*)/$10.53にマッチしませんが、€10.53にはマッチします。

RegExp Unicode Property Escapes

ドキュメントによると、

\p{…}および\P{…}形式のUnicodeプロパティエスケープが追加されます。
Unicodeプロパティエスケープは、uフラグの指定された正規表現で使用可能な新しいタイプのエスケープシーケンスです。
これを使うと、以下のような記述が可能になります。

constregexGreekSymbol=/\p{Script=Greek}/u;regexGreekSymbol.test('π');// true

Lifting template literals restriction

タグ付きテンプレートリテラルを使うと、エスケープシーケンスの制限を気にする必要がなくなります。
詳細はこちらをご覧ください。

What's new in ES2019?

ECMAScriptの最新バージョンである、ES2019で追加されたものを見ていきましょう。

Array.prototype.flat() / Array.prototype.flatMap()

Array.prototype.flat()は、指定された深さまでの配列を再帰的にフラット化します。
深さ引数のデフォルトは1です。
Infinityを指定すると、無制限にネストを解除します。

constletters=['a','b',['c','d',['e','f']]];// デフォルトは1letters.flat();// ['a', 'b', 'c', 'd', ['e', 'f']]// 2段階letters.flat(2);// ['a', 'b', 'c', 'd', 'e', 'f']// 2段 = 1段 * 2letters.flat().flat();// ['a', 'b', 'c', 'd', 'e', 'f']// ネストがなくなるまで再帰的にフラット化letters.flat(Infinity)// ['a', 'b', 'c', 'd', 'e', 'f']

Array.prototype.flatMap()は、引数の取り扱いはflat()と同じです。
配列を単純にフラット化するのではなく、関数を渡して任意の処理を行うことができます。

letgreeting=["Greetings from","","Vietnam"];// 普通のmapgreeting.map(x=>x.split(""));// ["Greetings", "from"]// ["", ""]// ["Vietnam"]// mapしてflatgreeting.flatMap(x=>x.split(""))// ["Greetings", "from", "", "", "Vietnam"]

普通にmap()を使うと、ネストした配列になります。
flatMap()を使うことでフラットな配列にすることができます。

Object.fromEntries()

Key-valueペアからオブジェクトに変換します。

constkeyValueArray=[['key1','value1'],['key2','value2']]constobj=Object.fromEntries(keyValueArray)// {key1: "value1", key2: "value2"}

Object.fromEntries()は引数として配列、Map、その他の反復可能プロトコルを受け取ります。
反復可能プロトコルの詳細についてはこちらをご覧ください。

String.prototype.trimStart() / .trimEnd()

String.prototype.trimStart()は文字列の先頭にある空白を削除し、String.prototype.trimEnd()は文字列の末尾にある空白を削除します。

letstr="    this string has a lot of whitespace   ";str.length;// 42str=str.trimStart();// "this string has a lot of whitespace   "str.length;// 38str=str.trimEnd();// "this string has a lot of whitespace"str.length;// 35

trimStart()のエイリアスとしてtrimLeft()が、trimEnd()のエイリアスとしてtrimRight()が存在します。

Optional Catch Binding

ES2019より前は、catch句に必ず例外変数を取る必要がありました。
ES2019では省略することができます。

// Beforetry{...}catch(error){...}// ES2019try{...}catch{...}

エラーを無視したいときに便利です。
この機能のユースケースについてはこの記事を強くお勧めします。

Function​.prototype​.toString()

関数の.toString()は、ソースコードを文字列として返します。

functionsum(a,b){returna+b;}console.log(sum.toString());// function sum(a, b) {//    return a + b;//  }

コメントも含みます。

functionsum(a,b){// perform a sumreturna+b;}console.log(sum.toString());// function sum(a, b) {//   // perform a sum//   return a + b;// }

Symbol.prototype.description

.descriptionSymbolの値を文字列で返します。

constme=Symbol("Alberto");me.description;// "Alberto"me.toString()//  "Symbol(Alberto)"

Download the cheatsheet

このリンクから、これらのチートシートをダウンロードすることができます。

よかったらAmazonLeanpubで私の本を買ったり、Educativeのコースを受けてみてください。

感想

全てと言いつつ全てではありませんが、これは元々著者がThe Complete Guide to Modern JavaScriptという書籍を出していて、この記事はその抜粋だからです。

抜粋といってもわりとけっこうな分量でしたが、元の本は全300ページという更に相当な力作となっています。
最初は変数や関数といった基礎部分から順にステップアップしていく内容で、この記事で紹介されている新機能は200ページ以降に出てくる、いわばおまけ部分です。
全編英語なのでなかなかたいへんですが、読み通せば現代のJavaScript事情に詳しくなれることは間違いありません。
しかもKindleなら2000円弱と大変お買い得、これは持ってて正解ですね。

一度入れたJavaScriptライブラリは二度とアップデートされることはない

以下はCloudflareによるレポート、JavaScript Libraries Are Almost Never Updated Once Installedの日本語訳です。

JavaScript Libraries Are Almost Never Updated Once Installed

Cloudflareは、WebページにJavaScriptやその他のフロントエンドリソースを配置するための一般的な方法である、CDNJSを支援しています。
今回我々は、CDNJSチームの許可を得て、CDNJSへのリクエストから匿名化・集約されたデータを収集し、インターネットでWebサイトがどのように構築されているかを分析することにしました。
今回のエントリでは、ひとつの疑問に焦点を当てています。
すなわち、いちどJavaScriptライブラリをサイトにインストールしたあとで、ライブラリは更新されるでしょうか?

まずは地球上でもっとも人気のあるライブラリ、jQueryから始めましょう。
このチャートは、過去12か月間の、jQueryのバージョンごとのリクエスト数を表しています。

01.png

2019年5月2日のリリース以降、バージョン3.4.1のリクエスト数は着実に増加しています。
しかし、古いバージョンが減少しているということはありません。
バージョン3.2.1は、チャートのはじめには一日あたり3600万のリクエストがありましたが、一年の終わりには2900万リクエストとなり、20%減少しています。
しかしこれは、平均的なWebサイトは2~4年しか保たないというリサーチの結果と一致しています。
バージョン3.4.1は着実に普及しつつありますが、しかしそれに伴って古いバージョンが減少するような傾向は全くありません。

ちなみにCDNJSに入っているjQueryの最も古いバージョンは、2013年5月25日にリリースされた1.10.0です。
これはいまだに一日平均10万件のリクエストがあり、それらを使っているサイトの人気も依然として衰えていないどころか、さらに高まっています。

02.png

jQueryだけの特異な現象でないことを示すために、別のプロジェクトTweenMaxも見てみましょう。

03.png

このパッケージは利用者がjQueryほど多くないため、データは一週間ごとの平均で平滑化されています。

バージョン1.20.4は1800万件のリクエストで始まり、1400万件で終わりました。
Webサイトの消滅割合と概ね同じ23%の減少です。
2.1.3の大きな成長は同時に、新バージョンのリリースが旧バージョンの人気にほとんど影響を与えないという明白な証拠を示しています。
2.1.3が2900万リクエストまで増加しても、旧バージョンへのリクエスト数は全く変わっていません。

04.png

結果として言えることは、一度公開されたライブラリを使うWebサイトは永久に存在し続けるということです。
従って、Web基盤プラットフォームは、あらゆるWebをサポートするためには、どれほど古いライブラリでも無期限に提供し続けなければなりません。

Cloudflareは、最新のWebにコントリビュートすることに非常に興味を持っています。
ぜひコメントで提案してください。

コメント欄

「CDNJS依存関係を自動でバージョンアップするRenovate Botってのを作ってる。」
「古いライブラリ使ってる人に通知するとか。」
「Wappalyzerみたいなかんじでバージョンが古いのを一目でわかるようにするとか。」
@majorみたいなセマンティックバージョンを提供するとか。」「マイナーバージョンで互換性なくすアプデしてくるライブラリを腐るほど見たからまあ無理だろ。」
「CDNが落ちてた時にローカルのライブラリを読ませてるんだけど、バージョン揃えるのが面倒。自動更新とかできない?」「ASP.NETだけどそういうのはある。」「jQueryにもあった気がする。」

感想

要するに、継続的なんちゃらだとかリーンなんちゃらだとか言ってるのは業界の極一部でしかなく、大半のサイトは一度作ったらそれっきりということです。
実際そんな最前線を走り続けなければならないサイトなんてほとんどありませんからね。
GAFAMでも配信サイトでもない一般人のサイトが最新技術を駆使してます!なんて言われてもどうでもよくない?

コメントで言っているセマンティックバージョンはこれのことです。
ざっくり言うと『互換性の無いバージョンアップはメジャーバージョンを上げろ』ですが、これが守られているライブラリなんてほとんど存在しないことは周知のとおりです。

といってもさすがに今どきjQuery1は、さすがに古すぎますね。
jQuery2が出たのが2013年4月。
当時小学校に入った子供が既に中学生になっているほどの昔ですよ。

あと今回見せてるのはjQueryとTweenMaxだけなので、VueとかReactとか最近のやつもちょっと見てみたかったですね。

【PHP8】もう`strpos($haystack, $needle)!==false`って書かなくていいんだ

ある文字列中に特定の文字列が存在するかを調べる方法としてstrposが存在します。

しかし、そもそもstrposは『ある文字列中で特定の文字列が何文字目に出てくるか』を調べる関数であり、第一に使用目的が異なる関数です。
そしてこちらも有名な話ですが、先頭が一致すると0が返ってくるので、緩やかな比較ではfalseと区別されません。
PHPのよくある落とし穴のひとつです。

if(strpos('放課後アトリエといろ','アトリエ')){echo'"放課後アトリエといろ"には"アトリエ"が含まれる';// 表示される}if(strpos('放課後アトリエといろ','放課後')){echo'"放課後アトリエといろ"には"放課後"が含まれる';// 表示されない!!}if(strpos('放課後アトリエといろ','放課後')!==false){echo'"放課後アトリエといろ"には"放課後"が含まれる';// 表示される}

マニュアルにも大きく警告が出されています。
01.png

この状況がついにPHP8で変わります

PHP RFC: str_contains

Introduction

str_containsは、ある文字列に特定の文字列が含まれているかをチェックし、見つかったか否かによってtrue/falseいずれかのbooleanを返します。

ある文字列に特定の文字列が含まれているかをチェックするために、一般的にはstrposstrstrが使用されます。
str_containsは凡そあらゆるプロジェクトで非常によく使われるユースケースなので、独自の関数を追加するに値するはずです。
strposstrstrには、いくつかの欠点が存在します。

・あまり直感的ではない
・間違いをしやすい(特に!==の場合)
・新規PHP開発者が覚えにくい

このため、多くのPHPフレームワークは独自のヘルパ関数を提供しています。
このことこそがstr_containsの重要性や必要性をよく示しています。

Proposal

このRFCは、新しい関数str_containsを提唱します。

str_contains(string$haystack,string$needle):bool

引数$haystack$needleを取り、$haystackの中に$needleが存在するか否かをbooleanで返します。

str_contains("abc","a");// truestr_contains("abc","d");// false// 空文字列はtrueになるstr_contains("abc","");// truestr_contains("","");// true

空文字列の扱いについて

As of PHP 8, behavior of '' in string search functions is well defined, and we consider '' to occur at every position in the string, including one past the end. As such, both of these will (or at least should) return true. The empty string is contained in every string.

PHP8において、文字列検索関数において''の動作は明確に定義されており、終端を含む文字列全ての位置にマッチします。そのため、これらは両方ともtrueを返すべきです。空文字はあらゆる文字列に含まれています。 - Nikita Popov

Case-insensitivity and multibyte strings

internalメーリングリストなどの議論において、この関数のマルチバイト対応版(mb_str_contains)は必要ないという結論に達しました。
理由として、この関数のマルチバイト版は非マルチバイト版と全く同じ動作になります。
文字列の見付かった位置によって動作が異なる場合は、マルチバイト版は異なる動作になります。
この関数は見付かった位置によって動作は変わらないので、マルチバイト版の必要はありません。

大文字と小文字を区別しない版については、需要が大文字と小文字を区別する版よりはるかに低いため、このRFCには含まれません。
区別しない版を取り込むと、有効なバリアントはstr_contains/mb_str_icontainsだけになります。
このように中途半端なバリアントをいきなり提供するとPHP開発者が混乱する可能性があるので、最初は小さく始めた方がよいでしょう。

Backward Incompatible Changes

PHP自体に後方互換性のない変更はありません。

ユーザランドに同じ関数が実装されている場合、非互換の可能性があります。
しかし、そのようなグローバル関数はアプリケーションの起動プロセスのかなり早い段階で追加されるので、開発者はこの問題にすぐ気付くでしょう。

Proposed PHP Version(s)

PHP 8

Implementation

str_contains関数は、このプルリクで実装されています。
https://github.com/php/php-src/pull/5179

投票

投票は2020/03/16まで、受理には2/3の賛成が必要です。
2020/03/09時点では賛成43反対5で、まず間違いなく受理されます。

感想

よく考えたらずっと前からあってもおかしくない関数なのに、これまで存在してなかったのは不思議ですね。

実際のところ、文字数としてはわずか2字の差にすぎません。

if(strpos($heystack,$needle)!==false){echo'$heystackに$needleが含まれている';}// 同じif(str_contains($heystack,$needle)){echo'$heystackに$needleが含まれている';}

しかし、読みやすさ、理解のしやすさという点では圧倒的にstr_containsに分がありますね。

以下はマルチバイト対応関数が用意されない点の補足です。
マルチバイト対応strposとしてmb_strposがありますが、文字が存在するか否かをチェックするだけであれば実はstrposでも問題ありません。

echostrpos('あい','い');// 3echomb_strpos('あい','い');// 1echostrpos('あい','あ');// 0echomb_strpos('あい','あ');// 0echostrpos('あい','う');// falseechomb_strpos('あい','う');// false

値が変わるのは『存在したときの文字数の数え方』だけです。
文字列の有無を確認するだけであれば、存在しない場合は常にfalse、存在する場合は常にint型となって差が出ないわけですね。

同様にstr_containsも、存在しない場合は常にfalse、存在する場合は常にtrueなるため、あえてmb_str_containsを用意する必要はないというわけです。

【MDN】非同期JavaScriptの紹介

MDNに非同期 JavaScriptという学習項目があるのですが、目次以外は日本語がありません。
ということで以下はIntroducing asynchronous JavaScript項目の日本語訳です。

Introducing asynchronous JavaScript

この記事では、同期JavaScriptにまつわる問題点を簡潔に要約します。
そして非同期JavaScriptのテクニックの幾つかを紹介し、それぞれが問題点の解決にどのように役立つかを示します。

前提条件:基礎的なコンピュータリテラシー、JavaScriptの基礎をある程度理解していること。
目的:非同期JavaScriptとは何か、同期JavaScriptとは何が違うのか、どのようなユースケースが存在するか、ということを理解する。

同期JavaScript

非同期JavaScriptが何であるのかを理解するためには、まず同期JavaScriptが何であるかを理解することから始めなければなりません。
このセクションでは、前回の記事で現れた情報の一部を要約します。

これまでに学習してきた多くの機能は、そのほとんどが同期です。
幾つかのコードを実行してみると、ブラウザはそれを実行すると同時に結果を返します。
簡単な例を見てみましょう。
実際の動作はこちらで、ソースはこちらで見ることができます。

constbtn=document.querySelector('button');btn.addEventListener('click',()=>{alert('You clicked me!');letpElem=document.createElement('p');pElem.textContent='This is a newly-added paragraph.';document.body.appendChild(pElem);});

このブロックは、処理が上から順番に実行されます。

  1. 利用可能な要素をDOMから探し出す。
  2. クリックしたときに発火するclickイベントリスナーを設定する。
    1. alert()でメッセージボックスを出す。
    2. アラートが消えたら<p>要素を生成する。
    3. そこにテキストを登録する。
    4. 最後にドキュメントボディに要素を追加する。

それぞれの処理が行われている間、他には何も起こりません。
レンダリングは一時停止されます。
これは、前回の記事で述べたように、JavaScriptがシングルスレッドであるためです。
ひとつのスレッドで一度に発生させることができるイベントはひとつだけであり、他のすべてはイベントが終了するまでブロックされます。

従って上記の例では、ボタンをクリックすると、その後アラートでOKボタンを押すまで、次の段落は表示されません。
実際に以下で試してみることができます。
https://codepen.io/pen/?&editable=true
https://jsfiddle.net/api/mdn/

注意:alert()は同期によるブロッキングを示すためのデモンストレーションとして使うのには最適ですが、実アプリで使用するととても残念なことになるので注意が必要です。

非同期JavaScript

前述のブロッキングといった問題を回避するため、多くのWeb APIは非同期コードを使って実行されるようになりました。
特に何らかの外部デバイスにアクセスしたりフェッチしたりするもの、たとえばネットワーク経由でファイルを取得する、データベースにアクセスしてデータを返す、Webカメラからビデオストリームにアクセスする、VRヘッドセットに出力をブロードキャストする、といったものです。

同期JavaScriptでこれらを実現するのが難しいのは何故でしょうか。
以下に簡単な例を見てみましょう。
サーバから画像を取得する場合、即座に結果を得ることはできません。
すなわち、以下の擬似コードはおそらく動作しないだろう、ということです。

varresponse=fetch('myImage.png');varblob=response.blob();// 画像を何処かのUIに表示する

理由は、画像のダウンロードにかかる時間がわからないためです。
時間がかかった場合、2行目に辿り着いた時点でresponseはまだ使用できません。
従って、時々あるいは毎回、2行目でエラーが発生することでしょう。
これを回避するため、responseを使用するときはresponseが使用可能になるまで待機しておく必要があります。

JavaScriptで扱える非同期コードのスタイルは主に2種類が存在します。
古いスタイルのコールバック形式と、Promiseを使った新しい形式のコードです。
以下のセクションでは、それぞれを順に解説します。

非同期コールバック

非同期コールバックは、バックグラウンドで動作するコードを呼び出すときにパラメータとして指定する関数です。
バックグラウンドコードは、実行が終了するとコールバック関数を呼び出して、自分が終了したことを知らせます。
あるいは特に注意すべきことが発生した際に通知します。

非同期コールバックは少々時代遅れになりつつありますが、いま一般に使われている歴史の長いAPIではまだまだ使用されています。
以下は非同期コールバック関数をaddEventListener()の第二引数に与える例です。

btn.addEventListener('click',()=>{alert('You clicked me!');letpElem=document.createElement('p');pElem.textContent='This is a newly-added paragraph.';document.body.appendChild(pElem);});

第一引数には監視するイベントのタイプを指定し、第二引数がイベントが発生したときに呼び出されるコールバック関数です。
コールバック関数をパラメータとして別の関数に渡す際、別の関数を実行した時点でコールバック関数まで実行されることはありません。
別の関数内部の何処かで非同期的にコールバックされます(これが名前の由来です)。
別の関数は、対象のイベントが発生したときにコールバック関数を実行します。

コールバック関数を含む関数は独自に実装することが簡単にできます。
ここではXMLHttpRequest APIを使った例を見てみましょう。
実際の動作はこちらで、ソースはこちらで見ることができます。

functionloadAsset(url,type,callback){letxhr=newXMLHttpRequest();xhr.open('GET',url);xhr.responseType=type;xhr.onload=function(){callback(xhr.response);};xhr.send();}functiondisplayImage(blob){letobjectURL=URL.createObjectURL(blob);letimage=document.createElement('img');image.src=objectURL;document.body.appendChild(image);}loadAsset('coffee.jpg','blob',displayImage);

引数のobject URLを表示するimgタグを作ってドキュメントのbodyに追加する関数displayImage()を作りました。
次いで、URLとコンテンツタイプ、そしてコールバックを引数として受け取るloadAsset()関数を作ります。
この関数は、XMLHttpRequest(よくXHRと略される)を使って指定されたURLのリソースをフェッチし、返ってきたレスポンスをコールバック関数に渡します。
loadAsset()関数にコールバックとして渡されたdisplayImage()関数はすぐに動作するのではなく、XHRによるリソースのダウンロードが完了するまで待機します。
待機はonloadイベントハンドラで実現されています。

コールバックは汎用性があります。
関数の実行順序や関数間で渡すデータを制御できるだけではなく、状況に応じて異なる関数を呼び出すこともできます。
レスポンスの内容によってprocessJSON()関数を実行したり、displayText()関数を実行したりといった様々なアクションを持たせることができます。

全てのコールバックが非同期であるわけではないことに注意してください。
以下はArray.prototype.forEach()を用いて配列項目をループする例です。
実際の動作はこちらで、ソースはこちらで見ることができます。

constgods=['Apollo','Artemis','Ares','Zeus'];gods.forEach(function(eachName,index){console.log(index+'. '+eachName);});

この例は、ギリシャの神々の配列をループし、インデックスと値をコンソールに出力するものです。
foreach()が期待するパラメータは配列インデックスと値の2値を引数として持つコールバック関数です。
ただし、このコールバック関数は一切待機せずに即座に実行されます。

Promises

Promiseは、モダンなWebAPIで使用される、新しいスタイルの非同期コードです。
よくある例はfetch() APIで、これは要するにXMLHttpRequestの現代版スタイルのようなものです。
サーバからのデータ取得から簡単な例を見てみましょう。

fetch('products.json').then(function(response){returnresponse.json();}).then(function(json){products=json;initialize();}).catch(function(err){console.log('Fetch problem: '+err.message);});

実際の動作はこちらで、ソースはこちらで見ることができます。

fetch()は取得したいリソースのURLひとつだけを引数に取り、そしてPromiseオブジェクトを返します。
Promiseは最終的に非同期操作が完了したかもしくは失敗したかのステータスを持つオブジェクトで、それまではどちらでもない中間状態になっています。
『わかり次第すぐに結果を返すことを約束する』という概念であるため、"promise"という名前が付いています。

この概念に慣れるのは少し練習が必要です。
これはシュレディンガーの猫に似ていると言えるかもしれません。
成功失敗どちらの結果もまだ発生していない状態では、fetch()はまだ実行を完了せず、次の操作を保留します。
結果がわかったときに、fetch()の次にある3つのコードブロックが呼び出されます。

・ふたつあるthen()ブロックは、いずれもその前の操作が成功した場合に呼び出されるコールバック関数です。
各コールバックは、その前の操作が成功した場合に返した結果を入力として受け取ります。
then()ブロックはそれぞれ別のpromiseを返すため、then()ブロックをいくつも連ねて、複数の非同期操作を順番に実行することができます。

catch()ブロックは、どこかのthen()ブロックが失敗したときに実行されます。
同期のtry...catchブロックと似たようなもので、catch()ブロックはエラーオブジェクトを受け取ります。
これは発生したエラーの種類を判断するために使用できます。
後ほど解説しますが、同期try...catchpromise内で使用することはできません。
async/awaitを使っている場合は使用可能です。

注:promiseについては後から詳しく解説するので、まだ完全に理解できてなくても大丈夫です。

The event queue

Promiseなどの非同期操作はイベントキューに入れられ、メインスレッドの処理が完了した後で実行されるため、後続のJavaScriptコードの実行をブロックしません。
キューに入れられた処理は、なるべく早めに処理が行われ、結果がJavaScriptに返されます。

Promises versus callbacks

Pormiseは、古い形式のコールバックと多少の類似点があります。
これらはコールバック関数を呼び出すオブジェクトであって、コールバックそのものを関数に渡す必要はありません。

しかしながら、Promiseは非同期処理を容易にするために設計されており、古い形式のコールバックよりも多くの利点が存在します。

・複数の非同期操作を.then()を使って連結し、最初の操作の返り値を次の操作の入力に渡すことができます。
これをコールバックで行うことは困難で、しばしばコールバック地獄と呼ばれる厄介なピラミッドになります。
・Promiseのコールバックは、必ずイベントキューに積まれた順番に処理されます。
・エラー処理は旧形式コールバックより遙かに優れています。ピラミッドの各レベルに個別のエラー処理を書く必要がなく、どのレベルでエラーが発生しても最後にあるひとつの.catch()で処理を受け取ることができます。
・コールバックを渡した時点で制御する権利を失う旧形式コールバックと異なり、Promiseは制御の反転を避けることができます。

The nature of asynchronous code

非同期コードの性質をさらに詳しく見てみましょう。
非同期コードの挙動を理解しないまま、非同期コードを同期コードと同じように記述した場合にどのような問題が起こるかを確認します。

以下のコードは、上で出てきたものと似ています。
異なるところは、コードの実行される順番を調べるためにいくつかのconsole.log()が埋め込まれていることです。
実際の動作はこちらで、ソースはこちらで見ることができます。

console.log('Starting');letimage;fetch('coffee.jpg').then((response)=>{console.log('It worked :)')returnresponse.blob();}).then((myBlob)=>{letobjectURL=URL.createObjectURL(myBlob);image=document.createElement('img');image.src=objectURL;document.body.appendChild(image);}).catch((error)=>{console.log('There has been a problem with your fetch operation: '+error.message);});console.log('All done!');

ブラウザがコードを実行すると、まず最初のconsole.log()Startingを表示し、次にimage変数をセットします。

その後は次の行に進んでfetch()ブロックの実行をはじめますが、fetch()は非同期実行されるため、メイン処理はPromise関連のコードより先に一番最後のconsole.log()まで辿り着き、All done!を出力します。

fetch()ブロックはファイルのフェッチ処理が終了すると、次の.then()ブロックに進み、It worked :)console.log()に出力します。
そのため、このメッセージはあなたが思っていたのと異なる順番で表示されることでしょう。

・Starting
・All done!
・It worked :)

よくわからない場合は、次の小さな例を考えてみてください。

console.log("registering click handler");button.addEventListener('click',()=>{console.log("get click");});console.log("all done");

これは、先ほどの例とよく似た動作をします。
最初と最後のconsole.log()メッセージは即座に表示されますが、2番目のget clickメッセージは、マウスのボタンをクリックするまで表示されません。
Promiseの例では、クリックされるのを待つかわりにリソースの取得が終わるまで待つということになります。

これらのトリビアルな例が示すように、非同期コードは処理順を認識しておかなければ問題を引き起こす可能性があります。
非同期コードブロックは、後で同期コードで使用するために結果を返すようなことはできません。
ブラウザが同期ブロックを処理するより前に、非同期コードが値を返すことを保証しません。

この動作を確認するために、最初の例の3番目のconsole.log()返り値を入れてみましょう

console.log('All done! '+image.src+'displayed.');

コンソールには、メッセージではなくエラーが表示されるはずです。

TypeError:imageisundefined;can't access its "src" property

ブラウザが3個目のconsole.log()を実行しようとした時点では、まだfetch()ブロックの動作が完了していないため、imageがまだ定義されていないからです。

注:セキュリティ上の理由から、ローカルのファイルに対してfetch()、もしくは類似の操作を行うことはできません。
上記の例をローカルで再現するためには、ローカルWebサーバを介して実行する必要があります。

Active learning: make it all async!

問題のあるfetch()の例を修正し、3個のconsole.log()が想定したとおりに実行されるためには、3番目のconsole.log()も非同期に実行する必要があります。
これは、2番目のブロックが終わった後に実行されるもうひとつの.then()ブロックを追加するか、あるいは単純に2つめの.then()ブロックの末尾に移動すれば修正できます。
今すぐ試してみてください。

注:行き詰まった場合は、こちらで答えを確認することができます。
あるいはライブに確認できます。
また、後のほうで出てくるGraceful asynchronous programming with Promisesにおいて、Promiseに関するより詳しい情報を知ることができます。

Conclusion

最も基礎的な部分では、JavaScriptは同期・ブロッキング・シングルスレッド言語であり、一度にひとつの動作しか実行することができません。
しかし、Webブラウザは同期実行されない関数を登録する関数およびAPIを提供しています。
そのかわり、これらは何らかのイベントが発生したとき(一定時間経過、ユーザ操作やマウス入力、ネットワーク経由のデータ到着など)に、非同期で呼び出す必要があります。
これらを使うことで、メインスレッドを停止したりブロッキングしたりすることなく、複数の処理を同時に実行させることができるようになります。

コードを同期的に実行するか非同期的に実行するかは、その目的によって決定すべきものです。

対象をすぐにロードして実行したい場合があります。
たとえばユーザ定義スタイルをWebページに適用したい場合、できるかぎり早くスタイルを取り込む必要があるでしょう。

しかし、データベースへのクエリやその結果を使ったテンプレートの作成など、時間のかかる操作を実行する場合は、これをメインスレッドから分離して、非同期にタスクを実行した方がよいでしょう。
時が経つにつれ、この場合は同期ではなく非同期を選択した方が理に叶っていたということがわかります。

In this module

一般的な非同期プログラミングの概念
非同期JavaScriptの紹介
協調型非同期JavaScript:タイムアウトとインターバル
Promiseによる洗練された非同期プログラミング
async/awaitによる簡単な非同期プログラミング
適切な技術を選択する

感想

MDNは、有象無象の氾濫するフロントエンド界隈において、最も信頼できる情報源のひとつです。
わけのわからない変なブログを見るくらいなら、MDNを見た方がよっぽど役に立つことが多いです。
まあ『最も』とか言っちゃうとソースやらW3Cやら見るのが最も適切だって話になるのですが、あんなもの一般人は読んでられませんしね。
あと、たまにMSDN(自動翻訳の混沌)と間違える。

本当はこの記事もMDNにコミットするつもりだったんだけど、構文の変換が想像以上に面倒だったので諦めました。
誰かかわりに送ってやって。

それにしても、ほぼ公式みたいなMDNが未だに訳されてないというのはびっくりですね。

【問題提起】マイクロソフト社の機械翻訳フィードバック対応はやはりおかしい

まず問題の翻訳を見てほしい

コーディング規則 - Visual Basic | Microsoft Docs

IsNot キーワードの使用

英語

Use the IsNot keyword instead of Not...Is Nothing.

自動翻訳(誤訳)

IsNot の代わりに Not...Is Nothing キーワードを使用します。

イベント処理

英語

Use Handles rather than AddHandler:

自動翻訳(誤訳)

Handles ではなく AddHandler を使用します。

記事にした経緯

どちらも見事に逆になっているわけです。

上の問題は こちらの記事で参照して気づきました。

下については @tfukumoriさんがフィードバックされています1
そしてこちらが Microsoft Localization Team からの回答です。

現在、機械翻訳されたコンテンツに対する翻訳の問題へのコントリビューションはできません。

人間が翻訳したコンテンツに関するフィードバックやご意見をお寄せください。人間が翻訳したコンテンツは機械翻訳エンジンをトレーニングするために使用されているため、お客様は、時間が経つにつれ人間および機械の両方による翻訳の質を向上させることに貢献できます。

人間が翻訳したトピックは、各コンテンツ ファイルのヘッダーの translationtype メタデータ値 "HT" (機械翻訳の場合は "MT") によって、機械翻訳されたトピックと区別できます。

そうですか。

貴重なフィードバックを放置して、何を契機にどんな改善を機械に期待しているのでしょうか。
一度飲み込んで五晩寝たものの、疑念が去らないので記事にしました。

※気になって仕方がないわけでも、怒っているわけでもありません。
 ただ、サイトの影響力の大きさから、(今回の件に限らず)誤った情報が広がってしまうのを心配しています。

つまりこういうことですね?

軍にAI兵士を導入しました。

兵卒「マイクロソフト上官、AI兵士が味方の兵士を次々撃ち殺しています」

MS上官「味方を誤射した兵士がいて、どうも真似しちゃったらしいんだ。誤射した兵士を鋭意教育中だから、そいつが成長してAIが学び直すまで待ってくれ」

ほかの機械翻訳では?

たとえば Google 翻訳はフィードバックを積極に活用しています。

Google 翻訳の改善に協力する - Google Translate ヘルプ
翻訳コミュニティ

最近話題になった悪用の懸念も悩ましいところですが、近年の精度の向上を見るに、一定の成果は上げているのではないかと思います。

今回のような「真逆のこと言ってるじゃん」ということは Google 翻訳でもあるわけですし、将来を見据えた意欲的な試みも歓迎される分野ですから、一概にどちらが優れているとは言えないと思います。

人々の期待

機械翻訳の精度が10年前と比べて格段に上がりました。
それに応じて人々の機械翻訳に対する信頼度も上がっています。
昔よりも信じやすくなっているわけですから、より高いレベルで改善・向上が求められているとも言えます。

本当に機械学習の問題?

ところで今回の件、本当に機械学習の問題でしょうか。

:flag_us: "A instead of B Nothing" ⇒ :flag_jp:「Aの代わりにB」:x:
:flag_us: "A rather than B" ⇒ :flag_jp:「AではなくB」:x:

機械学習がどうこう言うレベルの間違いではない気がします2
フィードバックをちゃんと読んだのだろうか。

これまたコーディング規約関連で、『型のメンバーの名前』に対する こちらのフィードバックでは、最終的に謝罪、訂正(ここでは「人間翻訳」化)となったわけですが、初動の機械回答感はひどいと言わざるを得ません。
Microsoft コミュニティのモデレータさんも、結構な頻度で同じ匂いのあしらい方をされますよね(こちらは役割が異なるとは思いますが)。
そういった文化、方針なんでしょうか。

機械ではなく人間の問題だと思います。
具体的には、担当部署の姿勢、企業のリソース割り当て(重要度の認識)の問題ではないでしょうか。
機械翻訳に完璧を求めるわけではありませんが、改善に協力しようと真っ当な指摘をした人に対して感謝と敬意を持ち、本当に機械学習の成熟度の問題なのか切り分けたうえで、可能な範囲で真摯に対応する姿勢は見せてほしいです。
というか、もったいないですよね? こんな凡ミス放置するの。

改元対応の問題でもそうでしたが、日本語ローカライゼーションが重要と位置づけられ、そこに適切なリソースが割り当てられているとはとても思えません。
ちょっと悲しいところです。

自省(頼りすぎ、期待しすぎ)

少し強い表現になってしまったかもしれません3
意見の偏りもあると思います。

そもそも私が機械翻訳に頼りすぎています。

特に正確さが求められる技術ドキュメントは、できるだけ原語で読むのが原則だと思います。
機械翻訳の精度が上がってきたこともあり、つい便利さに甘えてしまうことが増えました。
英文読解力はみるみる落ちてきました。

これではいかんと、最近はなるべく原語で読むよう(機械翻訳見ちゃったとしても最終的には原語にあたるよう)心がけています。

ということで、本当の問題はここにあったのかもしれません。


書き終えてから Microsoft コミュニティにこんなスレッドを見つけました。

日本語によるフィードバックは、ほぼ無視されているコトが発覚しました。 - マイクロソフト コミュニティ

本国MSでは、日本語のフィードバックを、自動翻訳で英語に訳して読んでいるらしい。


  1. 巻き込んですみません。念のため、この記事の内容は私の個人的な考えにすぎません。 

  2. 機械学習は専門外ですので、認識誤りをご指摘くださったら勉強させていただきます。 

  3. マイクロソフトさん、いつも製品使わせていただいています。ありがとうございます。 

【PHP8.0】gettypeとget_classの悪魔合体

ワレハget_debug_type、コンゴトモヨロシク…

PHPにはプリミティブ型名を取得するgettypeと、オブジェクトのクラス名を返すget_classという関数が存在します。
_があったりなかったりと命名の不統一も気になりますが、それよりgettypeはオブジェクトに使うとobjectしか返さず、get_classをプリミティブ型に使うとE_WARNINGが発生します。
いや、プリミティブ型であればintとかの型が欲しいし、オブジェクトならPDOとかの型が欲しいんだ、という問題に対する答えはありませんでした。

というわけで両者を合体させたget_debug_typeというRFCが提出されました。

PHP RFC: get_debug_type

proposal

このRFCは、指定された変数の型を返す新しい関数、get_debug_typeを追加する提案です。

これは、配列で来る値など、変数の型に基づいた既存のチェック方法では対応できないパターンを置き換えるためのものです。

$bar=$arr['key'];if(!($barinstanceofFoo)){// もっとも単純な例。しかしgettypeは"integer"を返すので、正しい型にしたいなら"int"に変換するなどが必要。thrownewTypeError('Expected '.Foo::class.' got '.(is_object($bar)?get_class($bar):gettype($bar)));}// 今後はこう書けるif(!($barinstanceofFoo)){thrownewTypeError('Expected '.Foo::class.' got '.get_debug_type($bar));}$bar->someFooMethod();

この関数は、正しい型名を返すという点でgettypeと異なります。
"integer"ではなく"int"を返し、クラスもクラス名に変換します。
次の表は、いくつかの値に対してgettypeget_debug_typeが返す値を比較したものです。

get_debug_type()gettype()
0intinteger
0.1floatdouble
trueboolboolean
falseboolboolean
"hello"string
[]array
nullnullNULL
Foo\BarFoo\Barobject
無名クラスclass@anonymousobject
リソースresource (xxx)resource
閉じたリソースresource (closed)

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

Implementation

https://github.com/php/php-src/pull/5143

投票

投票は2020/03/26まで、2/3の賛成で受理されます。
このRFCは賛成42、反対3で受理されました。

感想

Mark Randallは最初はgettypeがクラス名も返すようにしようとしたものの、Nikitaから「新しい関数にしてくれ」と言われてget_debug_typeを作ったようです。
まあ、これまでobjectとしか言わなかったgettypeがいきなり色々な型を喋り出したら困るところも出そうですからね。

ということで、今後は型の取得はget_debug_typeに一本化できそうです。

手間を省くための定型処理を言語機能に取り込むことは、他の言語でも多々起きていることです。
たとえばJavaScriptのasync/awaitPromiseの糖衣にすぎず、async/awaitができることは全てPromiseでもできるので、究極的にはasync/awaitは不要です。
しかし非同期処理を楽に書けるようにするために言語仕様に取り込まれました。
糖衣構文の取り込み自体は、このようにさほど珍しいことでもありません。

しかし、str_containsとかis_countableとか、他言語であれば「自分で書け」と言われそうな極端に簡単な構文まで言語仕様に取り込んでしまう言語は、PHP以外にはそうそう無いのいではないかと思います。

2000円のガラケーで快適にWebを表示する技術

PWA?SPA?WebAssembly?
うん、そうだね、よかったね。
それで、そのページは携帯で見れるのかい?

以下はAddy Osmani( Twitter / GitHub / Facebook / HP )による記事、Loading web pages fast on a $20 feature phoneの日本語訳です。
ちなみにこの人はGoogleのエンジニアで、Chromeの開発者のひとりです。

Loading web pages fast on a $20 feature phone

ヒント:高速なWeb基盤を構築することが、低価格のフィーチャーフォンにも、最新のハイエンドスマートフォンにも、全ての人々に良い体験を提供します。

Introduction

20~25ドル未満で購入できるフィーチャーフォンは、発展途上国において数億人が使用しているローエンドデバイスです。
それらはスマートフォンの低性能バージョンとも言えます。

低価格フィーチャーフォンは、CPUの性能が低く(ハイエンドスマホより6倍以上遅い)、RAMが少なく(通常4GB未満で、256-512MBも普通)、ストレージも少なく(4G)、そして大抵はタッチスクリーンもついておらず、ナビゲーションにはキーパッドや十字キーなどを使います。
たとえば以下のようなものです。
01.jpg

これらのデバイスは、ハイエンドスマホのようにリッチJavaScriptやメディアを処理することができません。
そのため、これらに送信するペイロードには特別な注意が必要です。

02.png

上記図はGeekbenchによる、2019年上半期に最も売れたスマートフォンのCPUベンチマークです。
人気のフィーチャーフォンであるNokia 3110のパフォーマンスを強調表示しています。

JavaScriptはシングルコアのパフォーマンスが重要であり(Webの他の技術と違い、JavaScriptは本質的にシングルスレッドです)、その性能はCPU性能に依存します
発展途上国について考えるときは、このデバイス特性に留意する必要があることを意味します。

この記事では、デバイスの性能にかかわらず誰もが利用できるサイトを構築することで、これらの問題に対処していきます。

Background

フィーチャーフォンは、スマートフォンが登場する前、2000年代半ばまで人気のあった端末であることを覚えている人もいるかもしれませんし、覚えていない人もいるかもしれません。
タッチスクリーンではなく小さなキーパッドを備えていて、電話、テキストメッセージ、テキスト中心のシンプルなWebブラウジングなど、かなり基本的な機能でできていました。
スマートフォンの登場後、これらの電話は先進国ではあまり見られなくなりました。

発展途上国では、誰もが4Gネットワークの無制限データプランでスマートフォンを利用できるわけではありません。
この市場は、スマートフォンから最低限の機能を抽出し、ハードウェア性能と価格の妥協の産物、スマートフィーチャーフォンによって成り立っています。

03.png

スマートフィーチャーフォン市場は2017年から伸びに伸びており、2019年には世界で4億台ものスマートフィーチャーフォンが販売されると想定されています

フィーチャーフォンの成長は、3110や8110(Paul Kinlanがデバッグガイドを公開しています)といった往年の名機をNokiaが復活させたことに支えられています。
インドでは、Reliance Jioが外出先でWebにアクセスするための、安価で最新のフィーチャーフォンを提供しています。
JiojはLinuxベースのフィーチャーフォン向けOSであるKaiOSを推進してきました。

フィーチャーフォン市場の成長により、効率的に動作するサイトが必要となってきており、そのためには注意すべき制約がいくつも存在します。

04.jpg

これはGoogle Images LiteとFacebook mBasicのサンプルです。
これらはいずれもフィーチャーフォンで快適に動作するようつくられており、クライアント側のスクリプト依存は最小限です。
ゲームであるProxxはスクリプトに多く依存しますが、フィーチャーフォンでも高速にロードするため積極的なコード分割設計を行っています。

Feature phone constraints

発展途上国のユーザは、3つの要因によって制限されています。
・低価格かつ高性能のデバイスはほとんどない
・高品質なネットワークがない
・手頃な価格のモバイルデータ通信がない

フィーチャーフォン向けサイトが必要であるならば、これらの制約に注意してください。

  1. ハードウェア
    フィーチャーフォンは大抵、低速(1.1GHz程度)のシングル・デュアルコアCPUと、512MB未満のRAMでできています。
    この制約が意味するところにおいては、8コアCPUと4GBのRAMでできているiPhone XSと比較してみるとよいでしょう。

  2. 通信量
    データプランは安くなりつつありますが、フィーチャーフォンが人気のある地域では依然として大きな制限があります。
    ページが高速に読み込まれ、費用が多くならないように、できるかぎりペイロードを小さくしましょう。

  3. スクリーンサイズ
    フィーチャーフォンの画面サイズは通常、スマートフォンのそれよりはるかに小さいです。
    2.4インチの大きさで、ほんの少ししか情報を出すことができません。
    必要なリソースをビューポート内のコンテンツにできるだけ早くロードすることを第一に考えてください。

  4. タッチスクリーン
    タッチスクリーンがないので、画面上の各機能、アクションボタン、リンクへはキーパッドから簡単にアクセスできる必要があります。
    必要以上にショートカットキーを埋め込む必要はありません。

  5. キーパッド
    フィーチャーフォンのキーパッドは、我々が使い慣れているQWERTYキーボードとは全く異なっています。
    およそ15個のボタンでできていて、多くの文字はボタンを何度も押さなければなりません。
    従って、タイピング量をできるだけ減らすUXを考える必要があります。

日本においても、データプランの上限がユーザエクスペリエンスに影響を与える可能性があります。

Development Guidelines

以下のTIPSは、フィーチャーフォン向けWebサイトに高速なエクスペリエンスを提供するのに役立つでしょう。
全体的に、ユーザが要求したもの以外のために待たせないでください。
JavaScriptのダウンロード時間と実行時間を可能な限り抑えてください。

Set performance budgets for your initial payloads

良好なパフォーマンスを確保するために開発チームが従わなければならない制限を決める、Performance budgetsという考え方があります。
超過することを許さない制限です。
開発を始める前に定量化可能なメトリックを定義し、新しい機能を追加しても総量が制限を超えないようにします。

Performance budgetsの例としては、JavaScriptのバンドルサイズ、画像のサイズ、HTTPリクエスト数などがあります。
ユーザエクスペリエンスを図るための指標として、First Contentful PaintLargest Contentful PaintFirst Input Delayなどを採用可能です。
対象ユーザに応じて、これらのメトリックごとに閾値を決めていきましょう。

Performance budgetsはアプリケーションロジック、vendorやcommonsのバンドル、そしてその他の項目ごとに設定可能です。
Lighthouseを使ったビルドプロセスや、継続的インテグレーションにおいて強制させることができます。

PRPL-30 - a JavaScript budget for feature phones

Chrome開発チームは、低速ネットワーク上のローエンドデバイスでもなるべく早く操作可能にするため、コード量を細かく制御するPRPLパターンを推奨しています。

PRPLは、ページを表示するために必要な最小限のスクリプトのみをプリロードし、それ以外はあとから遅延ロードし、そしてService Workerで今後のナビゲーション用のデータをキャッシュしておくことを勧めています。

PRPL-50は、イニシャルリソースを50KBに制限するという、Performance budgetsを発展させた考え方です。
フィーチャーフォンはCPUによる制約も受けるため、JavaScriptにさらに厳しい制限を課す必要があります。
フィーチャーフォンを対象とするWebサイトは、PRPL-30(30キロバイト)がよいと考えています。

05.png

この仮定においては、SSLネゴシエーションが終わった後、適切なCDNからレスポンスの最初のバイトが返ってくるまでの時間はおよそ2秒です。
従って、初回のペイロードがダウンロードされ、レンダリングされ、ユーザが画面の操作をできるまでの時間は約3秒となります。
すなわち、JavaScript中心のWebサイトの場合は、minifyとgzip圧縮を行った後のバンドルサイズを30キロバイト未満に縮小しなければならないということです。

06.jpg

待って待って。
30キロバイトだって?
ふざけてんの?
Reactのライブラリすら入れることができないんですけど!

厳しい制約のあるデバイス向けのサイトを構築するのであれば、ユーザエクスペリエンスを追求するために難しいトレードオフを行う必要があります。
たとえばReactをサーバ側にしか使わない、あるいはアプリケーションロジックを最小限にして遅延ロードを行わせる、Preactのようなソリューションを使う、などです。
これらのトレードオフについては後で解説します。

PRPL-30を守ることのできるアプリケーションの例としては、たとえば25KBのファイルサイズと5秒未満のインタラクション時間を持つProxxです。
独自の指標を目的とする際は、パフォーマンス予測ツールが役に立つでしょう。

遅延ロードするファイルのサイズも35KB未満が目安です。
30-35KBのチャンクサイズは、V8スクリプトストリーミングが並列処理を行うのに十分な大きさです。

Be Frugal with JavaScript

tl;dr:可能なかぎりスクリプトへの依存を最小限に抑え、静的レンダリングもしくはサーバサイドレンダリングを採用する。
クライアント側レンダリングやハイブリッドレンダリングが必須であれば、必要最小限のスクリプトのみをできるだけ少ない回数リクエストする。
プログレッシブリハイドレーションのようなテクニックも検討する。

07.png

JS is the #1 bottleneck on feature phones

フィーチャーフォン向けにインタラクティブな体験を提供する場合、JavaScriptが最大のボトルネックになる可能性が高いことに気を付けてください。
ページをレンダリングする技術の選択によって、ユーザが実際にページを操作できるようになるための時間が遅くなる可能性があるため、この選定は重要です。
サーバサイドレンダリング静的レンダリングを選んだ場合は、インタラクティブなペイロードはできるかぎり小さくしてください。

08.png

JavaScriptにはダウンロード時間および実行時間という、ふたつの大きなコストがあります。
ダウンロード時間は遅いネットワーク(3Gコネクションなど)によって伸び、遅いCPUは実行時間に遅延をもたらします。
下の図は、人気はあるが重いJavaScriptのあるサイトについて、CPUの種類による処理速度の違いを可視化したものです。
ハイエンドのスマートフォンに比べて、ローエンドのスマートフォンでは6倍もの実行時間がかかっていることがわかります。

09.png

これは即ち、レンダリングやインタラクティブ性を大きなJavaScriptバンドルに依存しているのであれば、フィーチャーフォンではユーザがUIを操作可能になるまで30から60秒待たされることすらあるということを意味します。

JavaScriptに必要なダウンロード時間と処理時間を最小限に収めるため、開発者はそれらのリソースをできるかぎり節約する必要があり、ユーザが必要とする可能性のあるルートやコンポーネントのためのJavaScriptだけを、それらが必要になったときにロードしなければなりません。

Keep interactive payloads lean

ペイロードを無駄にしないためにも、以下のことを守るべきです。

・画面外にあるコンポーネントやリソース、あるいはファーストビュー内でもクリティカルではないコンテンツについては、できるかぎり遅延ロードします。
・JavaScriptのコードを分割し、ファーストビューで必要なものだけを最初にロードするようにします。これによりダウンロードするスクリプトの量が減り、ページの読み込みが早くなります。
・バンドルから未使用コードを削除し、できるかぎり無駄のない状態にします。そのためにはバンドルを分析し、使用されていないライブラリや一部しか使われていないライブラリを差し替える必要があります。また、最初に使わないライブラリは遅延ロードさせる必要があります。
差分ロードを用いて、必要な機能のみをブラウザに提供するようにし、過剰なトランスパイルや過剰なポリフィルを避ける必要があります。モダンブラウザに送られるレガシーコードの量を減らすことで、読み込みのパフォーマンスを上げることができます。
・レンダリングや、ファーストビューのUIのためにJavaScriptを使っているのであれば、それらをプリロードしてください。以下のように記述することでそれが重要であることをブラウザに伝えることができ、ブラウザはできるだけ早くロードします。

<linkrel="preload"as="script"href="critical.js">

Choose your stack wisely

10.png

サードパーティ製ライブラリは開発を高速化し、複雑なタスクを容易に実現することに役立ちますが、その対価として重くなりがちです。
フィーチャーフォン向けに開発する場合は使用に注意しなければなりません。
以下のガイダンスを参考にしてください。

・フィーチャーフォンはリソースに大きな制約があるため、可能ならばJavaScriptフレームワークの使用そのものを避けるか、できるかぎり制限してください。
JavaScriptフレームワークは多大なオーバーヘッドを要求します。
Reactを使うのであればサーバサイドレンダリングするか、あるいはビルド時にPreact compatを使ってPreactに入れ替えて30キロバイト削減する、などの代替を検討してください。
Sveltelit-html、そしてVanilla JSなどはバンドルを軽くするための良い選択肢です。

・サードパーティライブラリへの依存をできるかぎり減らし、初期ロードに必要なバンドルサイズを削減してください。たとえばMoment.jsのかわりにdate-fnsluxonを使うなどです。bundlephobia.comなどでサイズのチェックができます。

・状態管理のためにReduxやStoreを使う場合は厳重な注意が必要です。
StateがしばしばHTMLにインライン展開され、レスポンスサイズが非常に大きくなってしまうことがあります。

Adapt to avoid loading heavy resources on slow connections

ヒント:このトピックの詳細については、adaptive loading - improving web performance on low-end devicesを参照してください。

11.png

Adaptive Loadingとは、提供するリソースをeffectiveTypeに基づいて変更する技術で、ブラウザからはNetwork Information APIで利用可能です。
Adaptive servingを使うことで、低速なユーザにも低機能であってもひとまずのエクスペリエンスを提供することが可能になります。

console.log(navigator.connection.effectiveType);// 3G

注意:たとえeffectiveTypeが"4G"であっても、スタバやカンファレンス会場のWifiのように実速度は低速である可能性は十分にあります。

Adaptive servingを使用すべき具体的な例としては、製品紹介ページなどがあります。
接続速度の遅いユーザには圧縮された商品画像だけを、高速なユーザには高品質な画像とJavaScriptによる強化された演出(画像のズームインや、別製品との比較を表示する機能など)を提供したりすることができるでしょう。

フィーチャーフォンにおいては、低速な回線が必ずしも最大の障害であるとは限りません。
遅いCPUや少ないメモリは、回線種別より多くの影響をユーザ体験に与えます。
Client Hintsは、CPUの性能にこそアクセスできないものの、デバイスのメモリ、Viewportの幅、ピクセルレート、ネットワーク情報、そしてその他の情報を集めることで、より詳細なサービス戦略を立てるために使うことができます。

Be respectful of users data plans with the Save-Data header

Android版Chromeにはライトモード(Data Saver)と呼ばれる機能があり、通信量を気にするユーザは、ブラウザがリソースを最適化してページの読み込み速度を向上させるオプションを選ぶことができます。
最適化には、画像を圧縮したり、重要でないリソースを後回しにしたり、ページをサーバ側でレンダリングすることなどが含まれています。
詳しくはChrome Lite Pagesをご覧ください。

対応しているブラウザでユーザがデータ節約モードを選択すると、あらゆるHTTP/HTTPSリクエストにSave-Dataリクエストヘッダ
アプリケーション開発者は、このヘッダがあれば重い機能をオフにするなど、データ節約モードをオンにしているユーザに最適化された体験を提供することができます。
JavaScriptでは以下のスニペットで確認可能です。

if("connection"innavigator){if(navigator.connection.saveData===true){// Implement data saving operations here.}}

12.jpg

注意:お使いのフィーチャーフォンはChromeをサポートしているかもしれませんが、お使いのChromeがライトモードをサポートしているとは限りません。

Offload costly app logic and state handling to Web Workers

SurmaによるTechniques to make a web app load fast, even on a feature phoneを読んでください。
素晴らしい投稿です。

ブラウザのメインスレッドでは、JavaScript以外にもページレイアウト、ピクセルの描画、ユーザインタラクションの追跡など多くの機能が動いています。
複雑で時間のかかるJavaScriptは、これら他のタスクをブロックしてしまう可能性があります。

Web Workersは、メインスレッドをブロックせずにバックグラウンドでJavaScriptを実行できる技術です。
複雑なアプリロジックや状態管理サービスなど、コストのかかるJavaScriptのオーバーヘッドをメインスレッドから切り離すことができます。
メインスレッドとワーカースレッドは、postMessage()onmessageハンドラでやりとりできます。
postMessageを使って、任意の値やオブジェクトを含むひとつの値を送り付けることができます。
実装の困難はComlinkのようなライブラリで軽減することができます。

ワーカースレッドを使った場合と使わなかった場合でProxxの差を調べた、Surmaのケーススタディは読みごたえがあります。
Nokia2(RAM1GB、1.3GHzクアッドコア)では、ワーカースレッドを使わなければ6.6秒もの間アプリがフリーズしました。
しかし、ワーカースレッドを使った場合は、応答するまでの時間は48ミリ秒でした。
CPUをごりごり使う処理を書いているのであれば、それをワーカースレッドに移植する価値は十分にあると言っていいでしょう。

Optimize Images

画像は多くのデータ量を消費します。
低価格デバイスでは特に、デコードにも時間がかかります。
従ってフィーチャーフォンに画像を配信する際は、適切に画像のサイズと圧縮方法を選ぶことが大事です。

Imageminのようなツールを使って、品質を下げずに画像のサイズを削減します。
アニメーションGIFは動画に変換しましょう。ロードがはるかに速くなります。しかしその前に、ローエンドデバイスがそんなに重いメディアファイルを必要としているか考えてください。
・可能なら画像を遅延ロードしますが、遅延ロードさせるJavaScriptライブラリが重くては意味がありません。ネイティブのloading属性が役立つでしょう。
・同じ画像を複数のサイズで用意しておき、ユーザのviewportに最も適したものを選択して提供するレスポンシブイメージが役立ちます。
画面に合った画像を提供します。低解像度のローエンドデバイスにはそれなりの画像を送ることで、より高速にデコードを行うことができます。

Detecting screen-size

現在は多くのスマホがQVGAで、解像度は320px * 240pxとなっています。
機能のオンオフやアダプティブローディングを画面サイズによって切り替えたい場合、以下のようなスニペットを使うことができます。

constisFeaturePhone=Math.min(screen.width,screen.height)<=240;

これは、Porxxが採用しているアプローチに似た方法です。

Emulate a feature phone in Chrome DevTools

低価格フィーチャーフォン向けサイトを構築するのであれば、格安端末の実物を入手することを強くお勧めします。

Chrome DevToolsでフィーチャーフォンをエミュレートするのであれば、以下の手順で可能です。

・Chrome DevToolsを開く
・Toggle Device Toolbarを選択
・デバイスのドロップダウンからEdit→Add custom deviceを選択
・名前をKaiOS(もしくは必要な対象名)にする
・幅240、高さ320を指定
・UAをMozilla/5.0 (Mobile; LYF/F90M/LYF-F90M-000-02-23-181217; Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5 YouTube/1.66.51.Jにする
・保存する
・(オプション)CPU性能をカスタマイズすることもできますが、これはあまり正確ではありません

14.png

Conclusion

何処にいる誰にでも、全てのユーザに楽しい体験を提供することは可能です。
ただし、全てのユーザのハードウェアが同じではないので、その点には注意が必要です。

端末の価格が手頃であるほど、CPUの性能も同等に低い可能性が高まります。
JavaScriptのパフォーマンスがダウンロード時間と実行時間に依存していることを考慮し、適切なエクスペリエンスを提供することを考えてください。

これはスマートフォンでも注意すべき点ですが、フィーチャーフォンではさらに重要になります。

コメント欄

「すごい有意義な投稿だった!」
「よく見たらChromeの中の人じゃん。有益な情報をありがとう」
「Svelteのパフォーマンスが高いと感じていたので言及してくれてうれしい。Preact試してみる!」「SvelteとSapperが大好き!」「Svelteや類似のフレームワークが早く普及してほしいね」
「バンドルサイズを抑える最も簡単な方法?簡単さ、JavaScriptを使わなければいい。」

感想

JavaScriptなんてほぼ動かないからHTML全部べた書きな、CSSもまともに効かないからfontタグを使え、とかそういうレベルの話かと思っていたら全然違っていて、わりと最近のAndroidフィーチャーフォンの話だった。
JavaScriptもCSSもきちんと認識してくれるけど、単に性能が低くてネットワークも遅いから、そういうところでも見れるように出力をカスタマイズしろ、という話ですね。

日本ではローエンドと言われているデバイス、安価な通信回線でも一般的なWebサイトを見るには十分な能力を持っているため、日本向けのWebサイトであれば正直そこまで気にする必要はありません。
しかし、たとえば日本では完全に無名のTranssionは、アフリカに特化したフィーチャーフォンを作って2000円で毎年1億台売っています。
アフリカは次の中国と期待される未知の市場です。
2000円の超ローエンドデバイスでも快適に見られるWebサイト作りが、世界に目を向けるならば必要となってくることでしょう。

ともあれ、ローエンドだろうがハイエンドだろうが無駄にアニメーションとスクロールを要求するサイトは滅ぶべきである。

【PHP8.0】throw文がthrow式になる

throw expressionというRFCが投票中です。

最初のアイデアは2019/12/06のSebastiaan Andewegによるツイート

それに対して2020/03/19にCarusoが反応し、そしてその日のうちにiluuu1994が最初のプルリクを出しました
はえーよ。

throw expression

Introduction

PHPのthrowは文であるため、アロー関数や三項演算子、NULL合体演算子などの式しか許されない場所から例外を投げることができません。
このRFCでは、それらを可能にするためthrow文を式に変更することを提案します。

Proposal

式を記述可能なあらゆるコンテキストでthrowが利用可能になります。
以下は思いついた便利そうな例です。

// アロー関数$callable=fn()=>thrownewException();// nullチェック$value=$nullableValue??thrownewInvalidArgumentException();// trueっぽいチェック$value=$falsableValue?:thrownewInvalidArgumentException();// 空ではない配列チェック$value=!empty($array)?reset($array):thrownewInvalidArgumentException();

他にも、議論の余地のある使用方法があります。
このRFCでは、以下のような記述も許可されています。

// ifを使った方が意図が明確になる$condition&&thrownewException();$condition||thrownewException();$conditionandthrownewException();$conditionorthrownewException();

Operator precedence

throwが式になると、優先順位を決める必要があります。
以下は現時点で有効な書式の例です。

throw$this->createNotFoundException();// こうなるthrow($this->createNotFoundException());// こうではない(throw$this)->createNotFoundException();throwstatic::createNotFoundException();// こうなるthrow(static::createNotFoundException());// こうではない(throwstatic)::createNotFoundException();throw$userIsAuthorized?newForbiddenException():newUnauthorizedException();// こうなるthrow($userIsAuthorized?newForbiddenException():newUnauthorizedException());// こうではない(throw$userIsAuthorized)?newForbiddenException():newUnauthorizedException();throw$maybeNullException??newException();// こうなるthrow($maybeNullException??newException());// こうではない(throw$maybeNullException)??newException();throw$exception=newException();// こうなるthrow($exception=newException());// こうではない(throw$exception)=newException();throw$exception??=newException();// こうなるthrow($exception??=newException());// こうではない(throw$exception)??=newException();throw$condition1&&$condition2?newException1():newException2();// こうなるthrow($condition1&&$condition2?newException1():newException2());// こうではない(throw$condition1)&&$condition2?newException1():newException2();

共通して言えるのは、全てがthrowキーワードより高い優先順位を持つということです。
このため、このRFCではthrowキーワードの優先順位を可能な限り低くすることを提案します。
現在有効なコードは、たとえ直感に反する動作だったとしても、今後も同じ動作をし続けます。
なぜなら、一般的にthrowは最後に使用するべき演算子であり、それ以降に記述した式は評価されないからです。

低い優先順位の唯一の欠点は、短絡評価のために括弧が必須になることです。

$condition||thrownewException('$condition must be truthy')&&$condition2||thrownewException('$condition2 must be truthy');// こうなる$condition||(thrownewException('$condition must be truthy')&&$condition2||(thrownewException('$condition2 must be truthy')));// こうではない$condition||(thrownewException('$condition must be truthy'))&&$condition2||(thrownewException('$condition2 must be truthy'));

もっとも、こんなコードはほぼ使われていないでしょう。

Backward Incompatible Changes

後方互換性のない変更はありません。

Other languages

C#では同じ文法が2017年に実装されました。

このような言語は他にはあまりありません。
ECMAScriptにプロポーザルがありますが、これは同じ問題を抱えているからです。

Proposed PHP Version(s)

PHP8。

投票

投票は2020/04/19まで、2/3の賛成で受理されます。
2020/04/06時点では賛成14、反対1で、受理される見込みです。

過去のML

9年前とか15年前にも同じ発想があったようですが、そのときは立ち消えになりました。
当時とはPHPのおかれた環境やRFCの出し方などがだいぶ異なることと、そしてなにより実物のプルリクがあるというのは大きいでしょう。

感想

ややこしいよね文と式。
全てが式になればいいのに。

というわけで、今後はもっと気軽にthrowすることができるようになります。
それどころかアロー関数で引数によって値を返したり例外Aを出したり例外Bを出したりすることもできちゃいますよ。
まあ正直、throwを出すようなややこしい式をアロー関数に書くんじゃないよと思ったりはするわけですが。

【PHP8.0?】PHPに名前付き引数が実装されるかもしれない

PHPのソースを眺めていたら、先日2020/04/07にNikitaがなんか面白そうなプルリクを出していました。
Named Parametersという2013年に提出されたまま忘れ去られたRFCがあるのですが、その機能を実装したものです。

どういう機能ってこういうのです。

functionhoge($foo,$bar){echo"foo=$foo,  bar=$bar";}hoge(bar=1,foo=2);// foo=2, bar=1

PythonCSharpなんかで実装されてるやつですね。

Nikita本人は機能が幾つも不足しているよと言っているのですが、不足の内容はOpcache対応や引数アンパックといった周辺機能で、基本的な機能は既に実装されているみたいです。

PHP RFC: Named Parameters

State of this RFC

これは名前付き引数についての準備的なRFCです。
このRFCの目的は、次のPHPバージョンで名前付き引数をサポートすべきか、またサポートするときはどのように実装すべきかを確認することです。
ここで解説している構文や動作は最終的なものではなく、詳細を詰めていく必要があります。

このRFCの実装はまだ完全なものではありません。
これは非常に複雑な機能なので、この機能が必要でなかったのに時間をかけて実装したくはありません。

Update 22-05-2014

私は他のことで忙しく、このRFCには取り組めていません。
このRFCはPHP6に間に合うように復活させるつもりです。
それまでの間、未解決の問題に対する議論の結果の一部をここにまとめておきます。

・名前付き引数のコンセンサスはまだ得られていません。
・名前付き引数のアンパックと、名前のない引数のアンパックは...構文に統合されます。アンパックのRFCは既に決定しているため、ここについて選択肢はあまりありません。
・継承時のパラメータ名チェックは強制されません。これはあまりにも大きなBC breakになるとはんだんされました。

主な未解決の実装上の問題は"Patch"セクションにリストアップされています。

What are named arguments?

名前付き引数とは、パラメータの順番ではなくパラメータ名を使って関数に引数を渡す方法です。

// 通常の引数array_fill(0,100,42);// 名前付き引数array_fill(start_index=>0,num=>100,value=>42);

名前付き引数に渡す引数の順番は自由です。
上記例では関数シグネチャと同じ順番で引数を渡していましたが、異なる順番で渡すこともできます。

array_fill(value=>42,num=>100,start_index=>0);

名前付き引数と名前のない引数を組み合わせることも可能であり、オプション引数の一部のみを名前付き引数で渡すことも可能になります。

htmlspecialchars($string,double_encode=>false);// ↓と同じhtmlspecialchars($string,ENT_COMPAT|ENT_HTML401,'UTF-8',false);

What are the benefits of named arguments?

名前付き引数の明白な利点の一つは、上のコードサンプルを見ればわかります。
変更したい引数までの間にある引数に対して、それぞれデフォルト値を指定する必要から解放されます。
名前付き引数があれば、変更したい引数だけを直接指定することができます。

これはデフォルト引数のRFCでも可能ですが、名前付き引数を使えば意図がより明白になります。
構文を比較してみてください。

htmlspecialchars($string,default,default,false);// vshtmlspecialchars($string,double_encode=>false);

ひとつめのコードを見ても、たまたまhtmlspecialcharsの引数を丸暗記していないかぎりfalseが何を意味するのかは分からないでしょう。

コードの自己文書化の利点は、オプション引数をスキップしないときにおいても明らかです。

$str->contains("foo",true);// vs$str->contains("foo",caseInsensitive=>true);

名前付き引数を使うことで、新しい方法で関数を使うことができるようにbなります。
引数を順番に並んだリストとしてだけではなく、キーと値のペアのリストとしても扱えるということです。
以下のような使い方が考えられます。

// 現在の構文$db->query('SELECT * from users where firstName = ? AND lastName = ? AND age > ?',$firstName,$lastName,$minAge);// 名前付き引数$db->query('SELECT * from users where firstName = :firstName AND lastName = :lastName AND age > :minAge',firstName=>$firstName,lastName=>$lastName,minAge=>$minAge);

Implementation

Internally

名前付き引数は、他の引数と同じくVM stackを通して渡されます。
これらの引数の違いは、位置引数は常にスタックの先頭に渡されるのに対し、名前付き引数は任意の順番でスタックに挿入することができるということです。
使用されないスタックの位置にはNULLが入り、引数カウントはNULLも数えます。

Errors

位置引数と名前付き引数を混在させることが可能ですが、名前付き引数は位置引数の後に配置しなければなりません。
そうしなければコンパイルエラーが発生します。

strpos(haystack=>"foobar","bar");// Fatal error: Cannot pass positional arguments after named arguments

可変長引数ではない関数に存在しない引数名を渡した場合、致命的エラーが発生します。

strpos(hasytack=>"foobar",needle=>"bar");// Fatal error: Unknown named argument $hasytack

同じ名前の引数を複数回渡した場合、新しい方で古い方が上書きされ、警告が発生します。

functiontest($a,$b){var_dump($a,$b);}test(1,1,a=>2);// 2, 1// Warning: Overwriting already passed parameter 1 ($a)test(a=>1,b=>1,a=>2);// 2, 1// Warning: Overwriting already passed parameter 1 ($a)

Collecting unknown named arguments

可変長引数の...$args構文を使った場合、余った名前付き引数は$argsに集められます。
名前付き引数は常に位置引数より後ろとなり、渡された順番が保持されます。

functiontest(...$args){var_dump($args);}test(1,2,3,a=>'a',b=>'b');// [1, 2, 3, "a" => "a", "b" => "b"]

使用例としては前述の$db->query()があります。

これはPythonで**kwargsと呼ばれている機能です。

Unpacking named arguments

引数アンパックのRFCは名前付き引数のアンパックにも対応します。

$params=['needle'=>'bar','haystack'=>'barfoobar','offset'=>3];strpos(...$params);// int(6)

文字列キーを持つ任意の値は、名前付きパラメータとして展開されます。
それ以外のキーは通常の位置引数として扱われます。

位置引数と名前付き引数をひとつの配列にまとめることも可能ですが、その場合でも引数の順番は守らなければなりません。
名前付き引数より後に位置引数が出てきた場合は警告がスローされ、アンパックは中止されます。

func_* and call_user_func_array

名前付き引数を使って、スタックから引数としてNULLが渡ってきた場合、func_*関数の挙動は以下のようになります。

func_num_args()はNULLを含んだ引数の個数を返す。
func_get_arg($n)はデフォルト値を返す。デフォルト値がない場合はNULL。
func_get_args()はデフォルト値を返す。デフォルト値がない場合はNULL。

3関数とも、未定義の引数名は無視します。
func_get_argsは値を返さず、func_num_argsはカウントに含めません。

call_user_func_arrayは名前付き引数をサポートしません。
文字列キーを持つ配列を渡すコードが壊れるからです。

Open questions

Syntax

現在の実装および提案では、名前付き引数について以下2種類の構文をサポートしています。

test(foo=>"oof",bar=>"rab");test("foo"=>"oof","bar"=>"rab");

ふたつめの構文は、引数名が予約語である場合のためにサポートされています。

test(array=>[1,2,3]);// syntax errortest("array"=>[1,2,3]);// works

この構文の選択は恣意的なもので、特に深く考えずに採用しました。
以下に、いくつか代替構文の提案があります(ほとんどはPhil Sturgeonによる提案です)。

// currently implemented:test(foo=>"oof",bar=>"rab");test("foo"=>"oof","bar"=>"rab");// キーワードを使えるtest($foo=>"oof",$bar=>"rab");test(:foo=>"oof",:bar=>"rab");test($foo:"oof",$bar:"rab");// キーワードを使えないtest(foo="oof",bar="rab");test(foo:"oof",bar:"rab");// 既に有効なコードなので不可test($foo="oof",$bar="rab");

どの構文で決定するかは議論次第です。

Collection of unknown named args into ...$opts

現在のRFCでは、位置引数と名前付き引数の両方がまとめて可変長引数の...$optsに入ってきます。
Pythonでは前者を*argsに、後者を**kwargsに入れるというアプローチをとっています。

Pros:PHPでは、Pythonではできない配列と辞書の混在ができます。
Cons:位置引数と名前付き引数を別にすることで、意図がより明確になります。必ずしも両方の引数をサポートしたいとはかぎらず、片方だけを強制したいかもしれません。

どのように扱うのが適切か、意見や議論を歓迎します。

Unpacking named args

引数アンパックについても同じ疑問が出てきます。
...$fooは位置引数と名前付き引数を一緒にできますが、*$foo**$fooに分けるべきでしょうか。

この決定は、可変長引数と同じに揃えるべきでしょう。

Signature validation allows changing parameter names

現在のところ、引数名はシグネチャに含まれていません。
位置引数しか使わない場合、これは合理的です。
引数名は関数の呼び出しに関係ないからです。

名前付き引数はこの動作を変更します。
継承先クラスが引数名を変更した場合、名前付き引数を使った呼び出しは失敗し、LSPに違反します。

interfaceA{publicfunctiontest($foo,$bar);}classBimplementsA{publicfunctiontest($a,$b){}}$obj=newB;// Pass params according to A::test() contract$obj->test(foo=>"foo",bar=>"bar");// ERROR!

名前付き引数が導入された場合、シグネチャの検証において引数名の変更にエラーを出さなければなりません。
通常の場合、インターフェイスと実装クラスの不一致は致命的エラーを発生させますが、名前付き引数において致命的エラーを出すとBC breakが大きくなりすぎてしまいます。
かわりに、より低いエラータイプ(WARNING / NOTICE / STRICT)を出すことを検討します。

これに関する具体的な議論のポイントをひとつ挙げておきます。
PHPは、実行時の動作を変更するini設定を導入する習慣を過去に置いてきました。
従って、この挙動をiniで制御できるようにすることは、私の選択肢にはありません。

Patch

差分がこちらにありますが、このパッチは不完全で、ダーティで、既知のバグがあります。

やるべき作業はまだまだ残っています。

・"Open questions"の結果を実装する。
・内部関数の全てのarginfosをドキュメントと一致するように更新する。現在のarginfo構造体は絶望的に時代遅れで、引数割り当てなどは自動的にできるようにしたい。
・内部関数において引数がスキップされたときに適切に動作するようにする。ほとんどの場合は自動的に動作するはずだが、手動調整が必要になる関数もかなりあるだろう。

感想

2014年とかPHP6とか出てくることからわかるように、このRFCはだいぶ昔に書かれてそのまんまです。
当時作成されたパッチはもはや使い物にならないため、新たにプルリクを作ってきたようです。
パッチにしろその他の内容にしろ、当時のPHPと今のPHPはだいぶ異なったものになっているので、なんにしろRFCのリファインは必要になるでしょう。

このプルリクについても、とりあえず提出されただけで何の展開もありませんし、メーリングリストでの議論も特に進んでいるわけではありません。
従って、このプルリクも今後どうなるかはわからず、再びこのまま忘れ去られるかもしれません。
しかしNikitaのことですから、いきなり完動品のプルリクが送られてきて第一線に躍り出る、なんてことがあっても驚きはないですね。

混沌の時代に実装された関数などでは特に、同じ内容の関数でも引数の順番が異なったりしていて大変なのですが、この機能が実装されたら、そのあたりを楽に処理できるようになります。
また却下されたデフォルト引数のRFCも、この名前付き引数があれば不要になります。
絶対にないといけないというほどでもないですが、存在すれば純粋に便利になる、そんな良い機能だと思います。

あとここからは完全に妄想ですが、このプルリクはジェネリクスへの足掛かりなのではないかと感じています。
Nikitaはどうもジェネリクス大好きっ子みたいですから、このプルリクを元に引数まわりに手を付けて、ついでにジェネリクスまでできるようにちゃったぜみたいなことを考えているのではないでしょうか。

Javaのテストにおけるモダンなベストプラクティス

本記事はModern Best Practices for Testing in Javaの日本語訳です。元記事の著者から許可を得て翻訳、公開しています。
翻訳は不慣れなので変なところもあると思いますが、ご容赦ください。


メンテナンスしやすくて読みやすいテストコードは良いテストカバレッジを確立するために重要で、それにより何かを壊すことを恐れずに新機能の実装やリファクタリングが可能になります。この記事には、私がJavaでユニットテストや統合テストを長年に渡って書いて得られた多くのベストプラクティスが含まれています。それにはJUnit5やAssertJ、Testcontainers、Kotlinといったモダンな技術も含みます。中には当たり前と思われるようなこともあるかもしれませんし、あなたがソフトウェア開発やテストについての本で読んだことと相容れないこともあるかもしれません。

TL;DR

  • ヘルパー関数やパラメータ化テスト、AssertJの強力なアサーションを多用し、変数を使いすぎず、関連することだけを検証し、滅多に起こらないようなケースに対してテストを書くことを避けることで、小さくて明確なテストを書きましょう。

  • 全ての関連があるパラメータを明確にし、データを正しく挿入し、継承よりもコンポジションを使うことで、自己完結しているテストを書きましょう。

  • 本番コードの再利用を避け、出力値とハードコードされた値との比較に焦点を当てることで、ダンプテスト1を書きましょう。

  • KISSの原則 > DRY原則

  • 完全な垂直スライドをテストする2ことに焦点を当て、インメモリデータベースの使用を避けて、本番環境に近いテストを書きましょう。

  • JUnit5とAssertJはとても良い選択です。

  • staticなアクセスを避け、コンストラクタインジェクションを使い、 Clock3を使い、非同期実行からビジネスロジックを分離することで、テストしやすい実装になるように労力を費やしましょう。

    基本

    Given, When, Then

    テストは、1行の空行で分けられた3つのブロックで構成されるべきです。コードのそれぞれのブロックはできるだけ短くするべきです。ブロックを短くするためにサブ関数を使いましょう。

  • Given(入力): データの生成やモックの設定のようなテストの準備

  • When(実行): テスト対象のメソッドや動作の呼び出し

  • Then(出力): 出力や振る舞いが正しいかどうか検証するためのアサーションの実行

// 良い例@TestpublicvoidfindProduct(){insertIntoDatabase(newProduct(100,"Smartphone"));Productproduct=dao.findProduct(100);assertThat(product.getName()).isEqualTo("Smartphone");}

“actual*” と “expected*” のプレフィックスを使う

// 悪い例ProductDTOproduct1=requestProduct(1);ProductDTOproduct2=newProductDTO("1",List.of(State.ACTIVE,State.REJECTED))assertThat(product1).isEqualTo(product2);

同じ値かどうかのアサーションで変数を使うなら、変数名のプレフィックスとして"actual” や “expected” を付けましょう。これによって読みやすくなり、変数の意図が明確になります。その上、期待値と実測値を混同してしまう恐れが減ります。

// 良い例ProductDTOactualProduct=requestProduct(1);ProductDTOexpectedProduct=newProductDTO("1",List.of(State.ACTIVE,State.REJECTED))assertThat(actualProduct).isEqualTo(expectedProduct);// 素晴らしくて明確

ランダム性のある値よりも固定値を使う

ランダム性のある値はテストを不安定にし、デバッグが困難になり、エラーメッセージが省略され、コードへのエラーの追跡が困難になるため避けましょう。

// 悪い例Instantts1=Instant.now();// 1557582788Instantts2=ts1.plusSeconds(1);// 1557582789intrandomAmount=newRandom().nextInt(500);// 232UUIDuuid=UUID.randomUUID();// d5d1f61b-0a8b-42be-b05a-bd458bb563ad

代わりに、全てに対して固定値を使用しましょう。固定値はテストの再現性を高くし、デバッグを容易にし、関連するコードの行への追跡を容易にするエラーメッセージが出力されます。

// 良い例Instantts1=Instant.ofEpochSecond(1550000001);Instantts2=Instant.ofEpochSecond(1550000002);intamount=50;UUIDuuid=UUID.fromString("00000000-000-0000-0000-000000000001");

ヘルパー関数を使用することで、タイピング量を減らすことができます。

小さくて明確なテストを書く

ヘルパー関数を多用する

細かいコードや繰り返し出現するコードをサブ関数に抽出し、それに説明的な名前をつけましょう。それはテストを短く保ち、テストの要点が一目で簡単に把握できるようになるという意味で強力です。

// 悪い例@TestpublicvoidcategoryQueryParameter()throwsException{List<ProductEntity>products=List.of(newProductEntity().setId("1").setName("Envelope").setCategory("Office").setDescription("An Envelope").setStockAmount(1),newProductEntity().setId("2").setName("Pen").setCategory("Office").setDescription("A Pen").setStockAmount(1),newProductEntity().setId("3").setName("Notebook").setCategory("Hardware").setDescription("A Notebook").setStockAmount(2));for(ProductEntityproduct:products){template.execute(createSqlInsertStatement(product));}StringresponseJson=client.perform(get("/products?category=Office")).andExpect(status().is(200)).andReturn().getResponse().getContentAsString();assertThat(toDTOs(responseJson)).extracting(ProductDTO::getId).containsOnly("1","2");}
// 良い例@TestpublicvoidcategoryQueryParameter2()throwsException{insertIntoDatabase(createProductWithCategory("1","Office"),createProductWithCategory("2","Office"),createProductWithCategory("3","Hardware"));StringresponseJson=requestProductsByCategory("Office");assertThat(toDTOs(responseJson)).extracting(ProductDTO::getId).containsOnly("1","2");}
  • データ(オブジェクト)の生成のため( createProductWithCategory())と、複雑なアサーションのためにヘルパー関数を使ってください。ヘルパー関数には、テストに関係のあるパラメータのみを渡すようにします。それ以外の値については、適切なデフォルト値を使ってください。Kotlinでは、デフォルト引数を使うことで簡単に実現できます。Javaでは、擬似的なデフォルト引数を実現するためにメソッドチェーンとオーバーロードを使う必要があります
  • 可変長引数はテストコードをより簡潔にしてくれます( ìnsertIntoDatabase()
  • ヘルパー関数はシンプルな値をより簡単に生成するためにも使えます。拡張関数を使うことができるKotlinなら、よりやりやすいです。
// 良い例 (Java)Instantts=toInstant(1);// Instant.ofEpochSecond(1550000001)UUIDid=toUUID(1);// UUID.fromString("00000000-0000-0000-a000-000000000001")
// 良い例 (Kotlin)valts=1.toInstant()valid=1.toUUID()

このヘルパー関数はKotlinではこのように実装します:

funInt.toInstant():Instant=Instant.ofEpochSecond(this.toLong())funInt.toUUID():UUID=UUID.fromString("00000000-0000-0000-a000-${this.toString().padStart(11, '0')}")

変数を使い過ぎない

複数回使われている値を変数に抽出することは、開発者の常用手段です。

// 悪い例@Testpublicvoidvariables()throwsException{StringrelevantCategory="Office";Stringid1="4243";Stringid2="1123";Stringid3="9213";StringirrelevantCategory="Hardware";insertIntoDatabase(createProductWithCategory(id1,relevantCategory),createProductWithCategory(id2,relevantCategory),createProductWithCategory(id3,irrelevantCategory));StringresponseJson=requestProductsByCategory(relevantCategory);assertThat(toDTOs(responseJson)).extracting(ProductDTO::getId).containsOnly(id1,id2);}

不幸にも、これはテストコードを著しく膨張させます。その上、得られるテスト失敗メッセージから、関連するコード行にさかのぼって値を追跡することは難しいです。

KISSの原則 > DRY原則

// 良い例@Testpublicvoidvariables()throwsException{insertIntoDatabase(createProductWithCategory("4243","Office"),createProductWithCategory("1123","Office"),createProductWithCategory("9213","Hardware"));StringresponseJson=requestProductsByCategory("Office");assertThat(toDTOs(responseJson)).extracting(ProductDTO::getId).containsOnly("4243","1123");}

テストコードが短く保たれていれば(それは強く推奨されます)、同じ値がどこで使われているのか知るのに何の支障もありません。それに加え、メソッドはさらに短くなり、それゆえに理解が容易です。そして最後に、この場合の失敗メッセージは、コードをさかのぼって追跡することをより簡単にしてくれます。

既存のテストを「ただもう一つ小さなことをテストするだけ」のために拡張しない

// 悪い例publicclassProductControllerTest{@TestpublicvoidhappyPath(){// 大量のコードがここに...}}

滅多に起こらないケースのテストを既存の(ハッピーパス4の)テストに追加することは魅惑的です。
しかし、そのテストは大きくて理解が難しいものになります。それは、その大きなテストによってカバーされる全ての関連するテストケースを把握することを困難にします。一般的に、こういったテストは「ハッピーパステスト」と呼ばれます5
もしこのようなテストが失敗した時、何が壊れたのか正確に理解するのは難しいです。

// 良い例publicclassProductControllerTest{@TestpublicvoidmultipleProductsAreReturned(){}@TestpublicvoidallProductValuesAreReturned(){}@TestpublicvoidfilterByCategory(){}@TestpublicvoidfilterByDateCreated(){}}

代わりに、期待する振る舞いについて全てわかる説明的な名前を持った新しいテストメソッドを作りましょう。はい、書く量は増えますが、関連する振る舞いだけをテストする、目的にぴったり合った明確なテストを作ることができます。繰り返しますが、ヘルパー関数はタイピング量を減らします。そして最後に、説明的な名前を持った、目的にぴったり合ったテストを追加することは、実装された振る舞いを記録する方法としてとても良いです。

テストしたいことだけをアサートする

本当にテストしたいことは何かということについて考えましょう。できるからといって、必要以上にアサートすることを避けましょう。さらに、前のテストにおいて既にテストしたことについて心に留めましょう。通常は、全てのテストにおいて同じことを何度もアサートする必要はありません。これによってテストが短く保たれ、明確に示され、期待する振る舞いについて気をそらされることがありません。

例について考えてみましょう。製品情報を返すHTTPエンドポイントについてテストします。テストスイートは下記のテストを含むべきです:

1 . データベースから取得した全ての値が正しいフォーマットで正しくマッピングされたJSONペイロードとして正しく返されることをアサートする大きな「マッピングテスト」。 equals()が正しく実装されているのであれば、AssertJの isEqualTo()(単一の要素用)または containsOnly()(複数の要素用)を使えば簡単にアサートできます。

StringresponseJson=requestProducts();ProductDTOexpectedDTO1=newProductDTO("1","evelope",newCategory("office"),List.of(States.ACTIVE,States.REJECTED));ProductDTOexpectedDTO2=newProductDTO("2","evelope",newCategory("smartphone"),List.of(States.ACTIVE));assertThat(toDTOs(responseJson)).containsOnly(expectedDTO1,expectedDTO2);

2 . クエリパラメータの ?categoryの正しい振る舞いをチェックするテスト。私たちは正しくフィルタリングされるかをテストしたいわけです、全てのプロパティが正しくセットされているかどうかではなく。それは上記のケースで既にテストしています。したがって、返された製品IDだけを比較すれば十分です。

StringresponseJson=requestProductsByCategory("Office");assertThat(toDTOs(responseJson)).extracting(ProductDTO::getId).containsOnly("1","2");

3 . 滅多に起こらないケース、または特別なビジネスロジックをチェックするテスト。たとえば、ペイロードの中の特定の値が正しく計算されているかどうか。このケースだと、興味があるのはペイロードのうち特定のJSONフィールドだけです。そのため、テスト対象のロジックのスコープを明確にして文書化するために、関連するフィールドだけをチェックすべきです。繰り返しますが、全てのフィールドを再度アサートする必要はありません、なぜならここでは関係ないからです。

assertThat(actualProduct.getPrice()).isEqualTo(100);

自己完結したテスト

関連するパラメータを隠さない(ヘルパー関数内)

// 悪い例insertIntoDatabase(createProduct());List<ProductDTO>actualProducts=requestProductsByCategory();assertThat(actualProducts).containsOnly(newProductDTO("1","Office"));

はい、データの生成とアサーションのため、ヘルパー関数を使うべきです。しかし、それらをパラメータ化しなければいけません。テストのために重要で、テストによって制御される必要がある全てに対してパラメータを定義しましょう。ソースを読む人に対して、テスト内容を理解するために関数定義にジャンプさせるようなことを強いてはいけません。経験則: テストメソッドのみを見ることでテストの要点がわかるようにすべきです。

// 良い例insertIntoDatabase(createProduct("1","Office"));List<ProductDTO>actualProducts=requestProductsByCategory("Office");assertThat(actualProducts).containsOnly(newProductDTO("1","Office"));

テストメソッドの中で正しくデータを挿入する

テストメソッドの中では全てが正しくある必要があります。再利用可能なデータの挿入コードを @Beforeメソッドに移動させることは魅惑的ですが、そうするとテストがどうなっているのか完全に理解するためには、ソースを読む人があちこち飛び回らなければならなくなります。繰り返しますが、データを挿入するヘルパー関数はこの繰り返し行うタスクを一行にすることの助けとなります。

継承よりもコンポジションを好む

テストクラスで複雑な継承階層を作ってはいけません。

// 悪い例classSimpleBaseTest{}classAdvancedBaseTestextendsSimpleBaseTest{}classAllInklusiveBaseTestextendsAdvancedBaseTest{}classMyTestextendsAllInklusiveBaseTest{}

このような階層は理解を難しくしますし、あなたは結局現在のテストに必要ないたくさんのものを含むベースのテストクラスを継承することになる可能性が高いです。これはコードを読む人の気を散らし、バグが発生するかもしれません。継承は柔軟ではありません: AllInklusiveBaseTestから継承したものを全てを使うことは不可能ですが、そのスーパークラスの AdvancedBaseTestから継承したものは何もないでしょうか?6その上、コードを読む人は全体像を理解するために複数のベースクラスの間を飛び回らなければなりません。

「重複は誤った抽象化よりは良い」
RDX in 10 Modern Software Over-Engineering Mistakes

代わりに、コンポジションを使うことを推奨します。それぞれの特定のフィクスチャの作業ごとに小さいコードスニペットとクラスを書きましょう(テストデータベースの起動、スキーマの生成、データの挿入、モックのウェブサーバの起動)。@BeforeAllを付与したメソッドの中か、もしくは生成されたオブジェクトをテストクラスのフィールドに割り当てることでこれらのパーツを再利用しましょう。それで、あなたはこれらのパーツを再利用することで全ての新しいテストクラスを組み立てます。まるでレゴブロックのように。この方法で、全てのテストは自身にぴったり合った、内容を把握するのが簡単でハプニングとは無縁のフィクスチャを持ちます。そのテストクラスは、全ての関連がテストクラスの中で正しいため、自己完結しています。

// 良い例publicclassMyTest{// 継承の代わりのコンポジションprivateJdbcTemplatetemplate;privateMockWebServertaxService;@BeforeAllpublicvoidsetupDatabaseSchemaAndMockWebServer()throwsIOException{this.template=newDatabaseFixture().startDatabaseAndCreateSchema();this.taxService=newMockWebServer();taxService.start();}}// 別のファイルpublicclassDatabaseFixture{publicJdbcTemplatestartDatabaseAndCreateSchema()throwsIOException{PostgreSQLContainerdb=newPostgreSQLContainer("postgres:11.2-alpine");db.start();DataSourcedataSource=DataSourceBuilder.create().driverClassName("org.postgresql.Driver").username(db.getUsername()).password(db.getPassword()).url(db.getJdbcUrl()).build();JdbcTemplatetemplate=newJdbcTemplate(dataSource);SchemaCreator.createSchema(template);returntemplate;}}

繰り返します:

KISSの原則 > DRY原則

ダンプテストは素晴らしい: 出力とハードコードされた値を比較する

本番コードを再利用しない

テストは本番コードをテストすべきです: 本番コードを再利用するのではなく。もしテストの中で本番コードを再利用すると、もはやそのコードはテストされない7ため、再利用されたコードによるバグを見落とすかもしれません。

// 悪い例booleanisActive=true;booleanisRejected=true;insertIntoDatabase(newProduct(1,isActive,isRejected));ProductDTOactualDTO=requestProduct(1);// 本番コードの再利用List<State>expectedStates=ProductionCode.mapBooleansToEnumList(isActive,isRejected);assertThat(actualDTO.states).isEqualTo(expectedStates);

代わりに、テストを書く時、入力と出力という観点から考えましょう。そのテストでは入力値をセットして、実際の出力値とハードコードされた値とを比較します。大抵の場合、コードの再利用は必要ありません。

// 良い例assertThat(actualDTO.states).isEqualTo(List.of(States.ACTIVE,States.REJECTED));

本番コードと同じロジックをテストで書かない

マッピングのコードはテストの中でロジックが再発明される、よくある例です。私たちのテストが、テストの最初に挿入されたエンティティと同じ値を含むことをアサートするのに使われるようなDTO戻り値を返すmapEntityToDto()メソッドを含むとしましょう。この場合、テストコードの中に本番コードと同じロジックを書いてしまう可能性が高いでしょう。それはバグを含むかもしれません。

// 悪い例ProductEntityinputEntity=newProductEntity(1,"evelope","office",false,true,200,10.0);insertIntoDatabase(input);ProductDTOactualDTO=requestProduct(1);// mapEntityToDto() は本番コードと同じマッピングロジックを含むProductDTOexpectedDTO=mapEntityToDto(inputEntity);assertThat(actualDTO).isEqualTo(expectedDTO);

繰り返しますが、解決策は actualDTOと、ハードコードされた値を含む手動で生成した参照オブジェクトを比較することです。それはとてもシンプルで、理解が容易で、エラーが発生しにくいです。

// 良い例ProductDTOexpectedDTO=newProductDTO("1","evelope",newCategory("office"),List.of(States.ACTIVE,States.REJECTED))assertThat(actualDTO).isEqualTo(expectedDTO);

もし全ての値の比較はしたくなくて、それゆえに完全な参照オブジェクトを生成したくなければ、サブオブジェクトのみか、関連する値のみを比較することを検討してください。

ロジックを書きすぎない

繰り返しますが、テストとはほとんど入力と出力に関するものです。入力を提供し、実測値と期待値を比較することです。したがって、テストの中でロジックを書きすぎる必要はないし、そうすべきではありません。もし多数のループと条件を伴うロジックを実装するなら、テストは内容を把握するのが難しく、よりエラーが起こりやすくなります。さらに、複雑なアサーションロジックの場合には、AssertJの強力なアサーションがあなたのために重労働をやってくれます。8

現実に近いテスト

完全な垂直スライドのテストに集中する

一般的に、モックを使ってそれぞれのクラスを個別にテストすることが推奨されます。しかし、それには深刻な欠点があります: あなたは全てのクラスを統合してテストしているわけではなく、内部のクラスごとにテストがあるため、内部のリファクタリングが全てのテストを破壊するでしょう。そして最終的に、あなたは様々なテストを書き、メンテナンスしなければなりません。
Screen Shot 2020-04-10 at 23.17.05.png
各クラスを分離してモックを使ってテストすることは欠点をもたらします。

代わりに、統合テストに注目することを提案します。「統合テスト」とは、全てのクラスを一緒にして(本番コードのように)、全ての技術レイヤー(HTTP、ビジネスロジック、データベース)を完璧な垂直スライドで通り抜けるテストのことです。この方法だと、実装ではなく振る舞いをテストします。これらのテストは正確で、本番環境に近く、内部のリファクタリングに対して堅牢です。理想的に、一つのテストを書くだけで済みます。

Screen Shot 2020-04-11 at 22.51.32.png
統合テストに注目することを推奨します(= 現実のオブジェクトを一緒に書き、一度で全てをテストする)

このトピックについては、言うことがもっとたくさんあります。詳細は私のブログ記事の「Focus on Integration Tests Instead of Mock-Based Tests」をチェックしてください。

テストのためにインメモリデータベースを使用しない

Screen Shot 2020-04-11 at 22.57.41.png
インメモリデータベースを使うと、本番環境と違うデータベースに対してテストすることになります

テストのためにインメモリデータベースを使うこと(H2,HSQLDB,Fongo)は信頼性とテストのスコープを減らします。インメモリデータベースと本番環境で使われるデータベースとは異なる振る舞いをし、異なる結果を返すかもしれません。そのため、未熟なインメモリデータベースを基にしたテストは、本番環境のアプリケーションの正しい振る舞いをする保証がありません。その上、あなたは確かな(データベース固有の)機能を使用(またはテスト)することができない状況に簡単にぶつかります。なぜなら、インメモリデータベースはサポートしていないか、もしくは異なる動作をするからです。これについての詳細は、「Don’t use In-Memory Databases for Tests」の記事をチェックしてください。

解決策は実際のデータベースに対するテストを実行することです。幸運にも、Testcontainersというライブラリが、テストコードの中でコンテナを直接管理するための素晴らしいJavaのAPIを提供しています。実行速度を速めるには、ここを見てください

Java/JVM

-noverify -XX:TieredStopAtLevel=1を使う

常に-noverify -XX:TieredStopAtLevel=1 JVMオプションを実行設定に追加しましょう。それによりテストが実行される前のJVMの起動時間が1〜2秒節約されます。これはIDE経由でテストを頻繁に実行する、テストの初期開発時に特に役に立ちます。

更新: Java 13から、 -noverifyは非推奨です。9
Tip: IntelliJ IDEAでは「JUnit」起動設定のテンプレートにこの引数を追加することができるため、新しい実行設定ごとに引数を追加する必要はありません。

screenshot-idea-run-config-template-default-vm-options-marked.png

AssertJを使う

AssertJは流れるような10型安全なAPIと、非常にバラエティに富んだアサーション、説明的なエラーメッセージを持った非常に強力で成熟したアサーションライブラリです。
あなたがしたいアサーションの全てがここにあります。これによりテストコードを短く保ちながら、ループや条件を持つ複雑なアサーションロジックを書かずに済みます。いくつかの例を示します:

assertThat(actualProduct).isEqualToIgnoringGivenFields(expectedProduct,"id");assertThat(actualProductList).containsExactly(createProductDTO("1","Smartphone",250.00),createProductDTO("1","Smartphone",250.00));assertThat(actualProductList).usingElementComparatorIgnoringFields("id").containsExactly(expectedProduct1,expectedProduct2);assertThat(actualProductList).extracting(Product::getId).containsExactly("1","2");assertThat(actualProductList).anySatisfy(product->assertThat(product.getDateCreated()).isBetween(instant1,instant2));assertThat(actualProductList).filteredOn(product->product.getCategory().equals("Smartphone")).allSatisfy(product->assertThat(product.isLiked()).isTrue());

assertTrue()assertFalse()を避ける

単純なassertTrue()assertFalse()によるアサーションは不可解なエラーメッセージを出力するため避けましょう:

// 悪い例assertTrue(actualProductList.contains(expectedProduct));assertTrue(actualProductList.size()==5);assertTrue(actualProductinstanceofProduct);
expected: <true> but was: <false>

代わりに、特にカスタマイズしなくても11、良いエラーメッセージを出力するAssertJのアサーションを使いましょう。

// 良い例assertThat(actualProductList).contains(expectedProduct);assertThat(actualProductList).hasSize(5);assertThat(actualProduct).isInstanceOf(Product.class);
Expecting:
 <[Product[id=1, name='Samsung Galaxy']]>
to contain:
 <[Product[id=2, name='iPhone']]>
but could not find:
 <[Product[id=2, name='iPhone']]>

もし本当にbooleanに対してチェックしなければならないのであれば、エラーメッセージを改善するためにAssertJのas()の使用を検討してください。

JUnit5を使う

JUnit5は(ユニット)テストのための最先端技術です。活発に開発され、多くの強力な機能(parameterized tests, grouping, conditional tests, lifecycle controlのような)を提供しています。

パラメータ化テストを使う

パラメータ化テストでは一つのテストを異なる値で複数回実行することができます。この方法では、テストコードを追加することなしに複数のケースを簡単にテストすることができます。そういったテストを書くための素晴らしい手段をJUnit5は提供します@ValueSource@EnumSource@CsvSource、そして@MethodSourceです。

// 良い例@ParameterizedTest@ValueSource(strings=["§ed2d","sdf_","123123","§_sdf__dfww!"])publicvoidrejectedInvalidTokens(StringinvalidToken){client.perform(get("/products").param("token",invalidToken)).andExpect(status().is(400))}@ParameterizedTest@EnumSource(WorkflowState::class,mode=EnumSource.Mode.INCLUDE,names=["FAILED","SUCCEEDED"])publicvoiddontProcessWorkflowInCaseOfAFinalState(WorkflowStateitemsInitialState){// ...}

私はこれらを広範囲に使うことを強く推奨します。なぜなら、最小限の努力でより多くのケースをテストできるからです。

最後に、パラメータで期待値もコントロールできる、より発展的なパラメータ化テストシナリオのために使用できる@CsvSource@MethodSourceのことを強調したいと思います。

@ParameterizedTest@CsvSource({"1, 1, 2","5, 3, 8","10, -20, -10"})publicvoidadd(intsummand1,intsummand2,intexpectedSum){assertThat(calculator.add(summand1,summand2)).isEqualTo(expectedSum);}

@MethodSourceは、全ての関連するテストパラメータと期待値を含む専用のテストオブジェクトと組み合わせて使うと強力です。残念ながら、Javaでは、これらのデータ構造(POJO)を書くことが面倒です。それが、下記でこの機能の例を示すのにKotlinのデータクラスを使う理由です。

data classTestData(valinput:String?,valexpected:Token?)@ParameterizedTest@MethodSource("validTokenProvider")fun`parsevalidtokens`(data:TestData){assertThat(parse(data.input)).isEqualTo(data.expected)}privatefunvalidTokenProvider()=Stream.of(TestData(input="1511443755_2",expected=Token(1511443755,"2")),TestData(input="151175_13521",expected=Token(151175,"13521")),TestData(input="151144375_id",expected=Token(151144375,"id")),TestData(input="15114437599_1",expected=Token(15114437599,"1")),TestData(input=null,expected=null))

テストをグループ化する

JUnit5の @Nestedはテストメソッドをグループ化するのに便利です。理にかなったグループは特定のタイプのテスト(InputIsXYErrorCasesのような)、またはテストの配下のそれぞれのメソッドを一つのグループにすることができます。(GetDesignUpdateDesign

publicclassDesignControllerTest{@NestedclassGetDesigns{@TestvoidallFieldsAreIncluded(){}@TestvoidlimitParameter(){}@TestvoidfilterParameter(){}}@NestedclassDeleteDesign{@TestvoiddesignIsRemovedFromDb(){}@Testvoidreturn404OnInvalidIdParameter(){}@Testvoidreturn401IfNotAuthorized(){}}}

screenshot-group-test-methods.png
JUnit5の@Nestedでグループ化

@DisplayNameまたはKotlinのバッククオートによる読みやすいテスト名

Javaでは、読みやすいテストの説明を書くのにJUnit5の@DisplayNameを使います。

publicclassDisplayNameTest{@Test@DisplayName("Design is removed from database")voiddesignIsRemoved(){}@Test@DisplayName("Return 404 in case of an invalid parameter")voidreturn404(){}@Test@DisplayName("Return 401 if the request is not authorized")voidreturn401(){}}

screenshot-displayname.png
JUnit5の@DisplayNameを使った読みやすいテストメソッド名

Kotlinでは、バッククオートの中にメソッド名を書くことができ、半角スペースを含むこともできます。これで冗長性を避けつつ読みやすくすることができます。

@Testfun`designisremovedfromdb`(){}

リモートサービスをモック化する

HTTPクライアントをテストするためには、リモートサービスをモック化する必要があります。私はそのためにOkHttpのWebMockServerを使うことを好みます。

MockWebServerserviceMock=newMockWebServer();serviceMock.start();HttpUrlbaseUrl=serviceMock.url("/v1/");ProductClientclient=newProductClient(baseUrl.host(),baseUrl.port());serviceMock.enqueue(newMockResponse().addHeader("Content-Type","application/json").setBody("{\"name\": \"Smartphone\"}"));ProductDTOproductDTO=client.retrieveProduct("1");assertThat(productDTO.getName()).isEqualTo("Smartphone");

非同期コードのアサーションのためにAwaitilityを使う

Awaitilityは非同期コードのテストのためのライブラリです。最終的に失敗するまで、どれくらいの頻度でアサーションを行うのか簡単に定義できます。

privatestaticfinalConditionFactoryWAIT=await().atMost(Duration.ofSeconds(6)).pollInterval(Duration.ofSeconds(1)).pollDelay(Duration.ofSeconds(1));@TestpublicvoidwaitAndPoll(){triggerAsyncEvent();WAIT.untilAsserted(()->{assertThat(findInDatabase(1).getState()).isEqualTo(State.SUCCESS);});}

この方法だと、不安定なThread.sleep()をテストの中で使うことを避けられます。

しかし、同期コードをテストすることのほうがはるかに簡単です。それが非同期実行と実際のロジックを分けるべき理由です。

ブートストラップDIは必要ない(Spring)

(Spring)DIフレームワークのブートストラップではテストが開始するまでに何秒かかかります。特にテストの初期開発の期間では、それによってフィードバックサイクルが遅くなります。

私が普段統合テストでDIを使わないのはそれが理由です。私は必要なオブジェクトを手動でnewを呼んでインスタンス化し、それらをまとめます。コンストラクタインジェクションを使っているなら、非常に簡単です。ほとんどの時間、あなたは自分が書いたビジネスロジックのテストをしたい。そのためにDIは必要ありません。一つの例として、統合テストについての私の投稿をチェックしてください。

一方で、Spring Boot 2.2では怠惰なBean初期化を簡単に使用できる機能が導入される予定で、DIベースのテストが著しくスピードアップするはずです。12

実装をテスト可能にする

staticアクセスを使わない。決して。これからも。

staticアクセスはアンチパターンです。第一に、それは依存性と副作用を難解にし、コード全体が理解しにくくなり、エラーが発生しやすくなります。第二に、staticアクセスはテスト容易性を害します。あなたはもはやオブジェクトを交換できません。しかしテストでは、あなたはモックまたは異なる設定を持った実際のオブジェクト(テストデータベースを指しているDAOのような)を使いたいはずです。

そのためstaticアクセスするコードの代わりに、staticではないメソッドにそのコードを書き、クラスをインスタンス化して、必要なオブジェクトのコンストラクタにそのオブジェクトを渡します。

// 悪い例publicclassProductController{publicList<ProductDTO>getProducts(){List<ProductEntity>products=ProductDAO.getProducts();returnmapToDTOs(products);}}
// 良い例publicclassProductController{privateProductDAOdao;publicProductController(ProductDAOdao){this.dao=dao;}publicList<ProductDTO>getProducts(){List<ProductEntity>products=dao.getProducts();returnmapToDTOs(products);}}

幸運にも、SpringのようなDIフレームワークがstaticアクセスを避ける簡単な方法を提供しています。私たちのために全てのオブジェクトの生成と配置を処理するからです。

パラメータ化

クラスの全ての関連する部分を、テストによってコントロール可能にしましょう。これは、この側面からコンストラクタのパラメータを作ることで実現できます。

たとえば、DAOのクエリ数の上限が1000になっているとします。この上限をテストするには、テストの中でデータベースエントリーを1001個生成することが求められるでしょう。この上限値をコンストラクタパラメータ化することで、その上限値が設定可能になります。本番環境では、このパラメータは1000です。テストでは、2を設定することができます。その上限機能のテストのために必要なテストエントリー数はたった3つだけで済みます。

コンストラクタインジェクションを使う

テスト容易性が低くなるため、フィールドインジェクションは邪悪です
あなたはテストの中でのDI環境のブートストラップ、またはハッキーなリフレクションマジックを使わなければなりません。
そのため、コンストラクタインジェクションは好ましい方法です。なぜなら、テストの中で依存するオブジェクトのコントロールが簡単になるからです。

Javaでは、少し定型文が必要です。

// 良い例publicclassProductController{privateProductDAOdao;privateTaxClientclient;publicCustomerResource(ProductDAOdao,TaxClientclient){this.dao=dao;this.client=client;}}

Kotlinでは、同じ内容がもっと簡潔になります。

// 良い例classProductController(privatevaldao:ProductDAO,privatevalclient:TaxClient){}

Instant.now()またはnew Date()を使わない

本番コードでInstant.now()またはnew Date()を呼び出すことで現在のタイムスタンプを取得するようなことをしてはいけません。あなたがその振る舞いをテストしたいのであれば。

// 悪い例publicclassProductDAO{publicvoidupdateDateModified(StringproductId){Instantnow=Instant.now();// !Updateupdate=Update().set("dateModified",now);Queryquery=Query().addCriteria(where("_id").eq(productId));returnmongoTemplate.updateOne(query,update,ProductEntity.class);}}

問題点は、その生成されたタイムスタンプをテストによってコントロールすることができないことです。テストの実行ごとに常に異なる値となるため、正確な値をアサートできません。代わりに、JavaのClockクラスを使いましょう。

// 良い例publicclassProductDAO{privateClockclock;publicProductDAO(Clockclock){this.clock=clock;}publicvoidupdateProductState(StringproductId,Statestate){Instantnow=clock.instant();// ...}}

テストの中で、あなたは今clockのモックを生成できるようになり、それをProductDAOに渡して、そのclockのモックが固定のタイムスタンプを返すように設定できます。updateProductState()を呼んだ後、定義されたタイムスタンプがデータベースに挿入されたかどうかをアサートします。

非同期実行と実際のロジックを分ける

非同期コードをテストするのはトリッキーです。Awaitilityのようなライブラリは助けとなりますが、それはまだ面倒で、テストはまだ不安定です。もし可能なら、(多くの場合は同期的な)ビジネスロジックを非同期実行から分割することは理にかなっています。

たとえば、ビジネスロジックをProductControllerの中に配置することにより、簡単な同期実行でそれをテストすることができます。非同期的で並列的なロジックはProductSchedulerに集約され、分離してテストできます。

// 良い例publicclassProductScheduler{privateProductControllercontroller;@Scheduledpublicvoidstart(){CompletableFuture<String>usFuture=CompletableFuture.supplyAsync(()->controller.doBusinessLogic(Locale.US));CompletableFuture<String>germanyFuture=CompletableFuture.supplyAsync(()->controller.doBusinessLogic(Locale.GERMANY));StringusResult=usFuture.get();StringgermanyResult=germanyFuture.get();}}

Kotlin

私のBest Practices for Unit Testing in Kotlinについての投稿は、Kotlinでテストを書くための多くのKotlin固有の推奨事項を含んでいます。


  1. [訳注] どういうテストなのかよくわからなかったのですが、どうやら期待値をハードコードするテストのことのように思えます。 

  2. [訳注] 統合テストのことのようです。 

  3. [訳注] java.time.Clockクラスのことです。 

  4. [訳注] 正常系みたいな意味のようです。 

  5. [訳注] ここでは一つのテストの大きさと内容理解の困難さを問題視しているわけですが、調べてみても「ハッピーパステスト」という言葉にはそのような意味はなさそうで、どういう意味なのか正直よくわかりません。ただ、この一文の意味がわからなくてもあまり支障はないのでそのままにしています。 

  6. [訳注] 意味がよくわからなかったのですが、AllInklusiveBaseTestから継承したメンバのうちAllInklusiveBaseTest由来のものなのか、AdvancedBaseTest由来のものなのか区別がつきにくい、つまり継承階層が深いと何がどこから継承されているのかわかりにくいということを言っているのだと推察します。もっとも、誤訳によって変な文になっているという可能性もありますが… 

  7. [訳注] ここでは期待値の生成のために本番コードを使っています。期待値の生成と実測値の生成の両方で本番コードを使っているため、実質的には何もテストしていないのと同じことになると考えられます。 

  8. [訳注] AssertJがやってくれるので、あなたが頑張ってロジックを書く必要はないということです。 

  9. [訳注] この「更新」は元記事で「Update」となっているのを忠実に訳したもので、元記事にもともとあったものです。今お読みになっている翻訳版の更新ではありません。 

  10. [訳注] fluentな。メソッドチェーンで書けるみたいな意味です。 

  11. 原文では「out-of-the-box」で、初期設定のままでも使えるみたいな意味です。後述の通り、AssertJではas()メソッドを使ってエラーメッセージをカスタマイズできますが、それをしなくても良いエラーメッセージを出しますよ、ということだと思います。 

  12. [訳注] 翻訳時点ではリリース済みです。おそらくこれです。 

【PHP8.0】PHPでアトリビュート/アノテーション/デコレータが書けるようになる

Attributes v2というRFCが投票中です。
投票期間は2020/05/04まで、投票者の2/3の賛成で受理されます。
2020/04/27時点では賛成48反対1で、ほぼ間違いなく可決されます。

Attributes v2

Introduction

このRFCは、クラス/プロパティ/関数/メソッド/引数/定数の宣言に、構造化されたアトリビュートをメタデータとして記述できるようにする提案です。
アトリビュートは、コードの宣言に直接設定ディレクティブを埋め込むことで定義されます。

同じような概念としてJavaのAnnotation、C#/C++/Rust/HackにおけるAttribute、Python/JavascriptにおけるDecoratorが存在します。

これまで、PHPではこのようなメタデータとしては非構造的であるdoc-commentsしか存在しませんでした。
しかしdoc-commentsはただの文字列であり、言語によって解釈されることはありません。
構造化された情報を保持するために、PHPの様々なコミュニティにおいて@ベースの疑似メタデータが考案されてきました。

ユーザランドでの使用例に加え、拡張機能などではコンパイルや構文解析、コード生成、実行時の挙動に影響を与えるようなアトリビュートの使用例も多く存在します。
下のほうに例を示します。

ユーザランドのdoc-commentsが多く使われていることから、この機能がコミュニティから強く求められていることがわかります。

Proposal

Attribute Syntax

アトリビュートは、既存の構文トークンT_SLT_SR、すなわち<<>>を使った特別なフォーマットのテキストです。

アトリビュートは言語内の多くの対象に適用されます。
・関数 (クロージャやアロー関数含む)
・クラス (無名クラス含む)、インターフェイス、トレイト
・クラス定数
・プロパティ
・メソッド
・関数/メソッド引数

以下は例です。

<<ExampleAttribute>>classFoo{<<ExampleAttribute>>publicconstFOO='foo';<<ExampleAttribute>>public$x;<<ExampleAttribute>>publicfunctionfoo(<<ExampleAttribute>>$bar){}}$object=new<<ExampleAttribute>>class(){};<<ExampleAttribute>>functionf1(){}$f2=<<ExampleAttribute>>function(){};$f3=<<ExampleAttribute>>fn()=>1;

アトリビュートはdoc-blockコメントと同様に、それが属する宣言の直前に記載します。
doc-blockコメントとどちらが先かといえば、コメントの前にも後ろにも書くことができます。

<<ExampleAttribute>>/** docblock */<<AnotherExampleAttribute>>functionfoo(){}

関数、クラス、メソッド、プロパティ、パラメータはひとつ以上のアトリビュートを持つことができます。

<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}

ひとつの対象に同じアトリビュート名を複数回指定することが可能です。

アトリビュートは1行で複数回宣言できます。

<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}

<<>>は式の接頭辞として使用されており、他言語で使用されているジェネリクスの一般的な構文<T>がもし今後導入されることになったとしても、それがアトリビュート構文と衝突することはありません。

構文についてはこのRFCで最も議論されてきたポイントであり、@:として表される新たな構文トークンT_ATTRIBUTEを導入する代替案も考えられました。
構文の選択はRFCの二次投票となります。

@:WithoutArgument@:SingleArgument(0)@:FewArguments('Hello','World')functionfoo(){}

この構文は、doc-blockコメントで一般的に見られる@を使用します。
欠点としては、空白を許可するとアトリビュートの終端を判別できなくなってしまうので、空白を禁止しなければならないことです。

最も要望されるであろう@[]が使えない理由については、下にあるAlternative Syntaxの議論を参照してください。

Attribute Names Resolve to Classes

アトリビュートは、コンパイル中にその時点でインポートされている全てのシンボルについて解決されます。
これはアトリビュートに名前空間を許容するためであり、別のライブラリやアプリケーションで同じアトリビュートが誤って再利用されるのを避けるためです。

useMy\Attributes\SingleArgument;useMy\Attributes\Another;<<SingleArgument("Hello")>><<Another\SingleArgument("World")>><<\My\Attributes\FewArguments("foo","bar")>>functionfoo(){}

このことはクラスにアトリビュートを宣言する際にも利点があります。
・リフレクションが解析しやすい。後述。
・静的解析ツールによる検証が容易になる。
・IDEがオートコンプリートや引数に対応することができる。

クラスアトリビュートを指定する例は以下のようになります。

namespaceMy\Attributes;usePhpAttribute;<<PhpAttribute>>classSingleArgument{public$value;publicfunction__construct(string$value){$this->value=$value;}}

Compiler and Userland Attributes

このRFCは、2種類のアトリビュートを区別しています。
・コンパイラアトリビュート:コンパイル時に検証される
・ユーザランドアトリビュート:リフレクションで検証される

コンパイラアトリビュートは、PhpCompilerAttributeに属する内部クラスです。

ユーザランドアトリビュートは、PhpAttributeに属するユーザランドのクラスです。

コンパイル時にコンパイラアトリビュートが見つかると、実行エンジンはアトリビュートに紐付けられているバリデーションのコールバックを呼び出します。
たとえばこのパッチにはPhpCompilerAttributeのバリデーションコールバックが含まれており、ユーザがPhpCompilerAttributeを使うのを防いでいます。

#include "zend_attributes.h"
voidzend_attribute_validate_phpcompilerattribute(zval*attribute,inttarget){if(target!=ZEND_ATTRIBUTE_TARGET_CLASS){zend_error(E_COMPILE_ERROR,"The PhpCompilerAttribute can only be used on class declarations and only on internal classes");}else{zend_error(E_COMPILE_ERROR,"The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead");}}INIT_CLASS_ENTRY(ce,"PhpCompilerAttribute",NULL);zend_ce_php_compiler_attribute=zend_register_internal_class(&ce);zend_compiler_attribute_register(zend_ce_php_compiler_attribute,zend_attribute_validate_phpcompilerattribute);

引数zvalは渡された全ての引数を含み、targetはアトリビュートが正しく宣言されているかを検証する為の定数です。
ユーザランドクラスはPhpCompilerAttributeを使用することができません。
使おうとするとエラーが発生します。

<?php<<PhpCompilerAttribute>>classMyAttribute{}// Fatal error: The PhpCompilerAttribute can only be used by internal classes, use PhpAttribute instead

アトリビュートをクラスツールにマッピングすることで、エディタやIDEは、アトリビュートの構文やコンテキスト情報を開発者に提供することができます。
この方法の欠点は、コンパイラアトリビュートがユーザランドアトリビュートであると誤って分類されてしまうことです。

Constant Expressions in Attribute Arguments

アトリビュートは定数AST式として評価されますが、これは引数を許可することを意味します。

<<SingleArgument(1+1)>><<FewArguments(PDO::class,PHP_VERSION_ID)>>

この主な使用例は、定数/クラス定数を参照することです。
定数を参照することで、既に定数として存在する情報を再定義する重複を避けることができます。
もうひとつの利点として、ツールやIDEによる静的解析でアトリビュートを検証できるということです。

定数ASTは、リフレクションを通してアクセスする際は値に解決されます。
これはかつて提出されたアトリビュートRFCとは意図的に異なる挙動です。

またパーサは、ビットシフト演算子とアトリビュート構文を区別できます。

<<BitShiftExample(4>>1,4<<1)>>functionfoo(){}

Reflection

ReflectionクラスにgetAttributes()メソッドが追加され、ReflectionAttributeインスタンスの配列を返します。

functionReflectionFunction::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionClass::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionProperty::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];functionReflectionClassConstant::getAttributes(string$name=null,int$flags=0):ReflectionAttribute[];

引数$nameがあれば指定したアトリビュート、もしくはそのサブクラスを含めたものを返します。

$attributes=$reflectionFunction->getAttributes(\My\Attributes\SingleArgument::class);

引数$flagが未指定の場合、getAttributes()メソッドは名前が完全一致したアトリビュートだけを返し、この動作がデフォルトです。
ReflectionAttribute::IS_INSTANCEOFを指定すると、instanceofを通過する全てのアトリビュートを返すようになります。

$attributes=$reflectionFunction->getAttributes(\My\Attributes\MyAbstractAttribute::class,\ReflectionAttribute::IS_INSTANCEOF);

ReflectionAttributeクラスは以下のようになります。

classReflectionAttribute{publicfunctiongetName():stringpublicfunctiongetArguments():arraypublicfunctionnewInstance():object}

アトリビュートの検証はReflectionAttribute::newInstance()でのみ行われるので、実は必ずしもアトリビュート名に対応したクラスを定義する必要はありません。
アトリビュート名と引数は直接ReflectionAttributeから取って来れます。

以下は完全な例です。

namespaceMy\Attributes{<<PhpAttribute>>classSingleArgument{public$argumentValue;publicfunction__construct($argumentValue){$this->argumentValue=$argumentValue;}}}namespace{<<SingleArgument("Hello World")>>classFoo{}$reflectionClass=new\ReflectionClass(Foo::class);$attributes=$reflectionClass->getAttributes();var_dump($attributes[0]->getName());var_dump($attributes[0]->getArguments());var_dump($attributes[0]->newInstance());}/**
string(28) "My\Attributes\SingleArgument"
array(1) {
  [0]=>
  string(11) "Hello World"
}
object(My\Attributes\SingleArgument)#1 (1) {
  ["argumentValue"]=>
  string(11) "Hello World"
}
**/

この使い方では、getAttributes()は決して例外をスローしません。
これにより、異なるライブラリが同じ名前のアトリビュートを定義していた際の問題を回避することができます。

Use Cases

Use Cases for PHP Extensions

アトリビュートの主な使用先は、PHPコアと拡張モジュールになるでしょう。

HashTablesへのアトリビュートは、全てのzend_class_entry/op_array/zend_property_info/zend_class_constantで使用可能です。

PHPコアや拡張モジュールは、ある定義にアトリビュートがあるかどうかチェックしたくなることがあるでしょう。
例としてOpcache JITに対する@jitのチェックなどです。
これは、関数やメソッドを常に最適化するようJITに指示します。

アトリビュートが実装されれば、拡張モジュールでは以下のように書けるようになります。

staticintzend_needs_manual_jit(constzend_op_array*op_array)returnop_array->attributes&&zend_hash_str_exists(op_array->attributes,"opcache\\jit",sizeof("opcache\\jit")-1));

開発者は、doc-commentのかわりにアトリビュートを使うことができます。

useOpcache\Jit;<<Jit>>functionfoo(){}

Other potential core and extensions use cases/ideas

以下はアトリビュートの使用法のアイデアです。
RFCの一部ではないことに注意してください。

関数/メソッドの非推奨。
アトリビュートを持つほぼ全ての言語にこの機能が組み込まれています。
PHPにこれがあれば、クラスやプロパティ、定数を非推奨にすることができます。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\Deprecated;<<Deprecated("Use bar() instead")>>functionfoo(){}

非推奨アトリビュートは、今のところtrigger_errorを使うことができません。

classFoo{<<Deprecated()>>constBAR='BAR';}echoFoo::BAR;// PHP Deprecated:  Constant Foo::BAR is deprecated in test.php on line 7

Reclassify Engine WarningsSupport Rewinding GeneratorsのRFCのようなレガシー動作を、オプトインで変更します。
Rustが似たような機能を持っています。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\Deny;usePhp\Attributes\Allow;<<Allow("rewind_generator")>>functionbar(){yield1;}<<Deny("undeclared_variables")>>functionfoo(){echo$foo;// PHP Fatal error:  Uncaught TypeError: Access to undeclared variable $foo}<<Deny("dynamic_properties")>>classFoo{}$foo->bar;// PHP Fatal error:  Uncaught Error: Invalid access to dynamic property Foo::$bar

Rustっぽいマクロの一部は、旧バージョンのPHPでのみPolyfillを読み込んだりするときに便利かもしれません。
ライブラリがOpcacheやpreloadingなどを条件付きで宣言するときに役立つでしょう。

// アイデアだよ。RFCの一部ではないよusePhp\Attributes\ConditionalDeclare;usePhp\Attributes\IgnoreRedeclaration;<<ConditionalDeclare(PHP_VERSION_ID<70000)>>// PHP7.0以上ならASTによって削除される<<IgnoreRedeclaration>>// 重複時はエラーを出さず単に無視するfunctionintdiv(int$numerator,int$divisor){}

最終的には、あるアトリビュートの引数を返すAPIや、全アトリビュートの一覧を返すAPIが含まれる予定です。
これによって拡張機能の作者は、最小限の労力でアトリビュートを使うことができるようになります。
以下は草案です。

/* アトリビュート名から引数一覧を返す */HashTable*zend_attribute_get(HashTable*attributes,char*name,size_tname_len);/* アトリビュートを返す */zval*zend_attribute_all(HashTable*attributes,char*name,size_tname_len);

Userland Use-Case: Declaring Event Listener Hooks on Objects

ユーザランドにおいて、アトリビュートは宣言に対する追加設定を宣言のすぐ傍に置くことができるという利点があります。
以下はSymfonyのEventSubscribersをアトリビュートを使ってリファクタリングする例です。
EventSubscriberInterfaceは、イベントをどのクラスのどのメソッドで処理するかをgetSubscribedEvents()で宣言する必要があります。

// 現在のコードclassRequestSubscriberimplementsEventSubscriberInterface{publicstaticfunctiongetSubscribedEvents():array{return[RequestEvent::class=>'onKernelRequest'];}publicfunctiononKernelRequest(RequestEvent$event){}}// リファクタした<<PhpAttribute>>classListener{public$event;publicfunction__construct(string$event){$this->event=$event;}}classRequestSubscriber{<<Listener(RequestEvent::class)>>publicfunctiononKernelRequest(RequestEvent$event){}}// アトリビュートを使ったイベントディスパッチャclassEventDispatcher{private$listeners=[];publicfunctionaddSubscriber(object$subscriber){$reflection=newReflectionObject($subscriber);foreach($reflection->getMethods()as$method){// Listenerアトリビュートを取得$attributes=$method->getAttributes(Listener::class);foreach($attributesas$listenerAttribute){/** @var $listener Listener */$listener=$listenerAttribute->newInstance();// $listener->eventはcallable$this->listeners[$listener->event][]=[$subscriber,$method->getName()];}}}publicfunctiondispatch($event,$args...){foreach($this->listeners[$event]as$listener){// 呼び出し$listener(...$args);}}}$dispatcher=newEventDispatcher();$dispatcher->addSubscriber(newRequestSubscriber());$dispatcher->dispatch(RequestEvent::class,$payload);

Userland Use-Case: Migrating Doctrine Annotations from Docblocks to Attributes

アトリビュートのRFCが考慮した主要なケースのひとつが、広く普及しているDoctrine Annotationsライブラリからの移行可能性です。

PHPコアがアトリビュートをサポートすることで、ユーザがDoctrine Annotationsからアトリビュートへ移行するための基盤を確保することができます。

このためこのRFCは、名前空間を使ったアトリビュートの操作が主な要件となっています。

Doctrineおよび任意のユーザランドライブラリは、親クラスの名前フィルタを利用して、興味のあるアトリビュートだけを抽出することができます。
提案のリフレクションAPIを使用し、独自のロジックを追加することで、より厳格なアトリビュートの使用を強制することができるようになります。

以下に、Doctrineのアノテーションと、このRFCのアトリビュートで同じことを実装した複雑なオブジェクトの例を示します。

<?phpuseDoctrine\ORM\AttributesasORM;useSymfony\Component\Validator\ConstraintsasAssert;<<ORM\Entity>>/** @ORM\Entity */classUser{/** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */<<ORM\Id>><<ORM\Column("integer")>><<ORM\GeneratedValue>>private$id;/**
     * @ORM\Column(type="string", unique=true)
     * @Assert\Email(message="The email '{{ value }}' is not a valid email.")
     */<<ORM\Column("string",ORM\Column::UNIQUE)>><<Assert\Email(array("message"=>"The email '{{ value }}' is not a valid email."))>>private$email;/**
     * @ORM\Column(type="integer")
     * @Assert\Range(
     *      min = 120,
     *      max = 180,
     *      minMessage = "You must be at least {{ limit }}cm tall to enter",
     *      maxMessage = "You cannot be taller than {{ limit }}cm to enter"
     * )
     */<<Assert\Range(["min"=>120,"max"=>180,"minMessage"=>"You must be at least {{ limit }}cm tall to enter"])>><<ORM\Column(ORM\Column::T_INTEGER)>>protected$height;/**
     * @ORM\ManyToMany(targetEntity="Phonenumber")
     * @ORM\JoinTable(name="users_phonenumbers",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
     *      )
     */<<ORM\ManyToMany(Phonenumber::class)>><<ORM\JoinTable("users_phonenumbers")>><<ORM\JoinColumn("user_id","id")>><<ORM\InverseJoinColumn("phonenumber_id","id",JoinColumn::UNIQUE)>>private$phonenumbers;}

アトリビュートは名前付きパラメータをサポートしていないため、少し制限があります。
これがアトリビュートが関数呼び出しのような構文を利用している理由ですが、もし名前付きパラメータがサポートされれば、自動的にこのRFCもその恩恵を受けることになります。

ユーザランドのアトリビュートへの移行には、Rectorのようなツールが役立ちます。

Criticism and Alternative Approaches

Alternative Syntax: Why not use @ or [] like other languages?

どうして@[]ではないの?

構文として<<>>を使用する理由は、コードのこの場所でまだ使用できる数少ない構文のひとつであり、その中でも自然に見えるものだからです。
接頭辞演算子としてまだ使われていない他の記号を使うことも可能ですが、実質的に使えそうなものは'%'くらいです。
他に使える記号というのは|=/などです。

@[]は、エラー抑制演算子と配列短縮構文に競合するため使用することができません。
以下のような構文は、現在既に有効なPHPコードです。

[[@SingleArgument("Hello")]]

この構文が配列宣言かアトリビュートかを決定するためには、無制限に先のトークンを調べなければならなくなる可能性があります。

Why not extending Doc Comments?

Doc Commentsをそのまま拡張するのではだめなの?

アトリビュートはDoc Commentsより優れています。

・名前空間により、同じDoc Commentsを使用している複数のライブラリによる競合を防ぎます。
・アトリビュートの存在チェックはo(1)のハッシュキーチェックであり、strstrやDoc Commentsのパースより高性能です。
・アトリビュートをクラスにマッピングすることで、アトリビュートが正しい構文であることを保証し、Doc Commentsの書き間違いによるバグの発生を減らします。
・アノテーションは非常に多くのツールやコミュニティで一般に使われているので大きな需要があります。しかし初心者がコメントと見誤ると混乱を招くことになるでしょう。また/*/**の違いもバグの原因になります。

PHPで既存のDoc Commentsを解析して構造化することは可能かもしれませんが、Doc Commentsの方言ごとに追加のパーサを呼び出す必要があります。
Doc Commentsは正しい文法になっていない可能性があるため、文法エラーの取り扱いを決めなければなりません。
最終的に、これはPHP内部に別の言語が存在するということになりそうです。
この方法は、アトリビュートを導入するより遙かに複雑となり、望ましくありません。

Why not always map attributes to simple arrays instead for simplicity?

シンプルに、アトリビュートは常に配列にマッピングするようにすればよくない?

アトリビュートのクラス名解決が何故重要なのか、その利点を前のセクションで解説しました。
アトリビュートが正しいかどうかの検証は、文法が正しいかどうかを検証できないDoc Commentsよりも大きなメリットがあります。

Why not a stricter solution like Doctrine Annotations?

Doctrine Annotationsのようにより厳密なソリューションは導入しないの?

このRFCは、PHPの基本的なアトリビュート機能のみを提案しています。
一般的なソリューションを全て解決するためには様々なユースケースを考慮しなければなりませんが、大抵の場合はDoctrineほど細かなシステムは必要ありません。

Why are nested attributes not allowed?

アトリビュートのネストが許可されないのは何故?

アトリビュートのネストとは、あるアトリビュートを他のアトリビュートの引数として定義するということを意味します。
これは引数でアトリビュートを宣言できるということなので、意図的に禁止されています。

Naming (attributes or annotations)

この機能の"アトリビュート"という名前は、既に使われているアノテーションとの混同を避けるために付けられました。
これによってDoctrine AnnotationsはPHP7ではDoc Commentsで実装され、PHP8ではアトリビュートで実装されている、といったことがおこります。

Backward Incompatible Changes

なし。

Proposed PHP Version(s)

PHP8.0

RFC Impact

To Core

トークン、ASTノード、zend_class_entry、zend_class_constant、zend_op_array、zend_property_infoの全てにアトリビュートを追加する必要があります。

To SAPIs

なし。

To Existing Extensions

なし。

JITは@JITではなくOpcache\Jitを、@nojitのかわりにOpcache\Nojitを使うことになる予定ですが、まだ未定です。

To Opcache

パッチに含まれており、100%の互換ではない可能性があります。

New Constants

なし。

php.ini Defaults

なし。

Open Issues

なし。

Future Scope

・名前付きパラメータとの統合
・下位互換性を壊すことなく、既存関数を新しい動作で拡張させる。
・関数/メソッドが呼ばれたとき、プロパティ/定数にアクセスしたときに非推奨を通知する<<Deprecated>>
・型付きプロパティとアトリビュートで、JSON/XMLからオブジェクトへの直列化がPHPコアでできるようになる。
<<«SingleArgument("foo"), MultiArgument("bar", "baz")>>を簡単に書けるような短縮構文。

Voting

2020/04/27時点で、導入には賛成48反対1で、ほぼ導入決定です。

構文は<<>>が40人、@:が10人で、<<>>に決まると思われます。

Patches and Tests

https://github.com/beberlei/php-src/pull/2<<>>
https://github.com/kooldev/php-src/pull/2@:

References

他言語でのアトリビュート/アノテーション/デコレータ。

Rust Attributes
C# Attributes
Java Annotation
TypeScript/ECMAScript Decorators
C++ Attributes
Go Tags
Attributes in Hack

かつて却下されたり放棄されたRFC。

Attributes v1
Annotations v2
Reflection Annotations using the Doc-Comment
Simple Annotations
Annotations in DocBlock RFC
Class Metadata RFC

感想

ほぼ全員賛成というのがちょっと信じがたいんだけど。
個人的にはどちらかというと賛成ですが、正直もっと意見が割れてもよさそうな提案ですよね。

ということで、PHP8からはアトリビュートが使えるようになります。
例を見るに、AttributeInterfeceをimplementsしたりとかも不要で、普通にクラスを書いたらいきなりアトリビュート名として使えるんですかねこれ。
ちょっとユーザレベルでの使い方がいまいちよくわかりませんでした。

というか正直、全体的に意味のよくわからないところが多々ありました。
私は普段ソースまで追ってないので、いきなりAST構文木とかzend_class_entryとか言われても知らんがな!ってかんじですよ。
きっと誰かが補足してくれるはず。

あと、それでは具体的にデフォルトでどんなアトリビュートが用意されてるの、ってのもRFCには書かれていません。
複雑なアトリビュートを定義するにはどうすればいいの、というのもRFCではちょっとよくわかりません。
このあたりは今後ドキュメントの追加をおねがいしたいところですね。
まあPHPのマニュアルは親切すぎて困るくらい丁寧なので、そのうち充実してくるとは思いますが。

【PHP8.0】StartsWith/EndsWithがPHP本体に実装される

先日PHP8でstr_containsが導入されることが決まったばかりですが、さらにもっと直接的な『〇〇で始まる』『〇〇で終わる』関数までも導入されることになりました。

Add str_starts_with() and str_ends_with() functionsというRFCが投票中です。
2020/05/04時点では賛成50反対4で、ほぼ導入確定です。

PHP RFC: Add str_starts_with() and str_ends_with() functions

Introduction

str_starts_withは、文字列が指定の文字列で始まるか否かをチェックし、結果をbool値で返します。
str_ends_withは、文字列が指定の文字列で終わるか否かをチェックし、結果をbool値で返します。

これらの機能は既存の文字列関数、たとえばsubstrstrpos/strrpos、そしてstrncmpsubstr_compare、あまつさえstrlenなどを駆使して実装されてきました。
これらユーザランドの実装には、様々な問題点があります。

str_starts_withとstr_ends_withの需要は高く、SymfonyLaravelYiiFuelPHP、そしてPhalconと、あらゆるフレームワークによってサポートされています。

文字列の始めと終わりをチェックすることは非常に一般的なタスクであり、簡単に行えるべきです。
多くのフレームワークがこのタスクを実装しているということは、このタスクを実行することが簡単ではないことを意味しています。
JavaScript/Java/Haskell/Matlabといった多くの高水準言語が標準でこの機能を実装している理由でもあります。
文字列の開始と終了をチェックすることは、これだけのためにフレームワークを導入したり、ユーザランドで最適ではない(どころかバグが入るかもしれない)実装を行ったりする必要のあるべき作業ではありません。

Downsides of Common Userland Approaches

この機能のアドホックな実装は、専用関数に比べると直感的ではありません。
PHPのニュービーや、他言語と同時開発する開発者にとっては特にそうです。
また、特に===を含む場合、実装を簡単に間違えます。
さらに多くのユーザランド実装はパフォーマンス上の問題があります。

注意:以下の実装には、E_WARNINGを防ぐために$needle === "" ||strlen($needle) <= strlen($haystack) &&のようなガードを入れましょう。

str_starts_with

substr($haystack,0,strlen($needle))===$needle

$haystackの無駄なコピーが発生するため、メモリ効率が良くありません。

strpos($haystack,$needle)===0

$needleが見つからなかった場合に$haystackを最後まで調べてしまうため、CPU効率が悪くなります。

strncmp($haystack,$needle,strlen($needle))===0// genericstrncmp($subject,"prefix",6)===0// ad hoc

これは効率的ですが、$needleの文字数を別に渡す必要があり冗長です。

str_ends_with

substr($haystack,-strlen($needle))===$needle

str_starts_with同様、メモリ効率がよくありません。

strpos(strrev($haystack),strrev($needle))===0

str_starts_with同様CPU効率が悪いだけでなく、文字列反転処理まで入るので、さらに非効率です。

strrpos($haystack,$needle)===strlen($haystack)-strlen($needle)

冗長であり、CPURL効率が悪くなることがあります。

substr_compare($haystack,$needle,-strlen($needle))===0// genericsubstr_compare($subject,"suffix",-6)===0// ad hoc

効率的ですが、冗長です。

Proposal

2つの関数、str_starts_with()str_ends_with()を導入します。

str_starts_with(string$haystack,string$needle):boolstr_ends_with(string$haystack,string$needle):bool

str_starts_with()は、$haystack$needleで始まるかどうかを調べます。
strlen($needle) > strlen($haystack)であれば即座にfalseを返し、そうでなければ両文字列を比較し、先頭一致すればtrueを、一致しなければfalseを返します。

str_ends_with()も同じですが、後方一致です。

以下に例を示します。

$str="beginningMiddleEnd";if(str_starts_with($str,"beg"))echo"printed\n";// trueif(str_starts_with($str,"Beg"))echo"not printed\n";// falseif(str_ends_with($str,"End"))echo"printed\n";// trueif(str_ends_with($str,"end"))echo"not printed\n";// false// 空文字if(str_starts_with("a",""))echo"printed\n";// trueif(str_starts_with("",""))echo"printed\n";// trueif(str_starts_with("","a"))echo"not printed\n";// falseif(str_ends_with("a",""))echo"printed\n";// trueif(str_ends_with("",""))echo"printed\n";// trueif(str_ends_with("","a"))echo"not printed\n";// false

空文字に関しては、受理済のstr_containsのRFCの挙動に従います。
これはJavaやPythonなどと共通の動作です。

Backward Incompatible Changes

ユーザランドに同名の関数がある場合は競合します。

Proposed PHP Version(s)

PHP8

RFC Impact

・SAPI:全てのPHP環境に関数が追加されます
・エクステンション:無し
:Opcache:無し
・New Constants:無し
・php.ini Defaults:無し

Votes

投票は2020/05/04まで。
投票者の2/3+1の賛成で受理されます。

Patches and Tests

https://github.com/php/php-src/pull/5300

References

他言語の類似機能
・JavaScript: String#startsWith() / String#endsWith()
・Python: str#startswith() / str#endswith()
・Java: String#startsWith() / String#endsWith()
・Ruby: String#start_with?() / String#end_with?()
・Go: strings.HasPrefix() / strings.HasSuffix()
・Haskell: Data.String.Utils.startswith / Data.String.Utils.endswith
・MATLAB: startsWith()) / endsWith()

bugs.php.net
bug #50434 / bug #60630 / bug #67035 / bug #74449

過去のRFC
PHP RFC: rfc:add_str_begin_and_end_functions

Rejected Features

大文字小文字を区別しない版とマルチバイト版は、以前のRFCには含まれていましたが、このRFCでは廃止されました。
理由はstr_containsを参照してください。

感想

PHPの文字列関数ってやたら大量に用意されてるわりに意外と基本的なところが抜けていたのですが、PHP8でstr_continsとこの関数が追加されたことによって、テキスト処理に必要なものは出揃うことになったのではないでしょうか。

他に必要なのって何かありますかね。デフォルト関数の命名規則とか?

【PHP8.0】なんでもあり型が書けるようになる

ジェネリクスではない…ジェネリクスではないのだよ………

ざっくり言うとvar_dump()の型引数です。
var_dumpにはプリミティブ値にオブジェクトにリソース型にと、どんな値でも渡すことができるのですが、PHP7.4時点の型システムではvar_dumpの引数の型を表すことができません。
PHP8.0で導入予定のunion型を使うとarray|bool|callable|int|float|null|object|resource|stringとなるのですが、実はresource型はPHP8.0でもまだ使えないので、mixed型を完全に再現することはできません。

ということでMixed Type v2のRFCが提出されました。
投票は2020/05/21まで、受理には2/3+1の賛成が必要です。
が2020/05/11時点では賛成35反対6で、おそらく受理されます。

Introduction

PHP7のスカラー型、7.1のnull許容型、7.2のobject型、そして最新8.0のUNION型とPHPの型システムは進化し続けており、PHPの開発者はほとんどの関数において引数と返り値、そしてプロパティについて明示的に型情報を宣言することができるようになりました。

しかし、PHPは常に型をサポートしてきたわけではありません。
そしてこれは、型情報が欠落している際にその意味が曖昧になってしまうという問題に繋がります。

・特定の型に決まっているが、プログラマが宣言を忘れてしまった
・特定の型に決まっているが、古いバージョンのPHPと互換を保つためにあえて省略している
・現在のPHPの型システムでは表現できない型である

明示的なmixed型を用意することで、引数や返り値、プロパティに型を追加して、型情報を忘れていたわけではなく、正確に指定できなかったりあえて広げているのだという主張をを示すことができます。

現在のところmixed型はPHPDocの中でのみ使用することができますが、これは適切ではありません。
PHPDocでmixed型が使用されている顕著な例としては、PHP標準ライブラリ関数の返り値などがあります。
ネイティブにmixed型があれば、これらをより正確に表現することができるでしょう。

またmixed型はPHPマニュアルにおいても広く使用されています。

var_dump(mixed$expression[,mixed$...]):void

Proposal

PHPの型システムにmixed型を追加します。
これはarray|bool|callable|int|float|null|object|resource|stringと等価です。
これはPHPの継承時の型検査の実装に適合する正しい動作です。

LSP, Covariance and Contravariance

このProposalは、リスコフの置換原則に準拠しています。

PHP7.4以降、PHPは共変戻り値と反変パラメータに対応しています。

PHPではLSP原則に従うように、パラメータの拡大を許容しています。
サブクラスにおいて、親クラスより広い、特殊でない型を使用することができます。

PHPではLSP原則に従うように、返り値の縮小を許容しています。
サブクラスにおいて、親クラスより狭い、特殊な型を使用することができます。

Parameter types are contravariant

引数は、特定の型からmixed型に拡大することができます。

// 正しい例classA{publicfunctionfoo(int$value){}}classBextendsA{// intからmixedに拡大は許可publicfunctionfoo(mixed$value){}}

引数の縮小は、LSP原則に違反するため許可されません。

// 不正な例classA{publicfunctionfoo(mixed$value){}}classBextendsA{// mixedからintに縮小は不可// Fatal errorが出るpublicfunctionfoo(int$value){}}### Return types are covariant返り値は`mixed`型から特定の型に縮小することができます```php// 正しい例classA{publicfunctionbar():mixed{}}classBextendsA{// mixedからintに縮小は許可publicfunctionbar():int{}}

返り値の拡大は、LSP原則に違反するため許可されません。

// 不正な例classC{publicfunctionbar():int{}}classDextendsC{// intからmixedに拡大は不可// Fatal errorが出るpublicfunctionbar():mixed{}}

Property types are invariant

プロパティ型指定のRFCに従い、プロパティの型は不変です。

// 不正な例classA{publicmixed$foo;publicint$bar;public$baz;}classBextendsA{// プロパティ型は縮小不可// Fatal errorが出るpublicint$foo;}classCextendsA{// プロパティ型は拡大不可// Fatal errorが出るpublicmixed$bar;}classDextendsA{// 未指定にmixed型を追加するのも駄目// Fatal errorが出るpublicmixed$baz;}classEextendsA{// 型指定の削除も駄目// Fatal errorが出るpublic$foo;}

Void return type

void型の返り値については、LSPに適合していたとしても拡張は許可されません。

classA{publicfunctionbar():void{}}classBextendsA{// Fatal error: Declaration of B::bar(): int must be compatible with A::bar(): voidpublicfunctionbar():int{}}

このRFCは、既存の振る舞いに従います。
すなわち、void型をmixed型に広げることはできません。

Signature checking of function when no parameter type present

引数に型が存在しない場合の型チェックは、mixed型が指定されたかのように動作します。

classA{// 引数の型がないのでmixedとみなすpublicfunctionfoo($value){}}classBextendsA{// mixed型を追加したが、親クラスと動きは同じpublicfunctionfoo(mixed$value){}}classCextendsB{// mixed型を削除したが、親クラスと動きは同じpublicfunctionfoo($value){}}classDextendsB{publicfunctionfoo(mixed$value=null){}}

現在のところ、これはクラスの継承に限った動作です。

PHPで型を定義できるようになれば、他のところでも動くようになるかもしれません。

Signature checking of function when no return type present

返り値に型が指定されていない場合の型チェックは、mixed|void型が指定されたかのように動作します。

サブクラスでオーバーロードする際は、返り値を未指定にするか、void型にするか、mixed型およびそのサブタイプの何れかを指定しなければなりません。
そして、一度変更したあとの型を未指定に戻すことはできません。

classA{// 返り値に型がないのでmixed|voidとみなすpublicfunctionfoo(){}}classBextendsA{// mixedを指定した。voidは禁止になるpublicfunctionfoo():mixed{}}classCextendsB{// mixed|voidはmixedより広いのでNG// Fatal errorが出るpublicfunctionfoo(){}}classDextendsB{// voidはmixedのサブタイプではないのでNG// Fatal errorが出るpublicfunctionfoo():void{}}

The mixed|void union type

このRFCは、mixed|voidのUNION型は必要ないので許可しないという立場です。
今後ユースケースが見つかれば許可される可能性はあります。

Nullability

mixed型にはnullが含まれます。
従ってmixed型のnull許容型は情報の重複となります。

このRFCは、mixed型のnull許容型を許容しないという立場です。
今後ユースケースが見つかれば許可される可能性はありますが、その際はどの冗長な型指定を許可して、どの冗長な型指定は許可しない、という議論が必要になるでしょう。

// NG、既にnull許容なのでfunctionfoo(?mixed$arg){}// NG、既にnull許容なのでfunctionbar():?mixed{}

Explicit returns

返り値にmixed型を使用する場合、明示的にreturnを記述する必要があります。
さもなければTypeErrorが発生します。

functionfoo():mixed{}foo();// Uncaught TypeError: Return value of foo() must be of the type mixed, none returned

既存のnull許容型と同じ動作です。

functionbar():?int{}bar();// Uncaught TypeError: Return value of bar() must be of the type int or null, none returned

Resource 'type'

PHPではresource型の値を変数に割り当てることができますが、ユーザランドでは引数、返り値、プロパティの型としてresource型を使用することができません。
このRFCの立場としては、resource型はresource型チェックをパスすべきである、というものです。

Mixed vs any

PHPではマニュアルやPHPStanなどの静的解析ツールで広くmixed型が使われているため、mixedになりました。
またmixedはPHP7以降弱い予約語とされていますが、anyは予約語に含まれません。

RFC Impact

Proposed PHP Version(s)

PHP8.0

Backward Incompatible Changes

クラス名としてのmixedが禁止されますが、PHP7.0以降mixedは弱い予約語です。

To SAPIs

特になし。

To Existing Extensions

特になし。

To Opcache

特になし。

Vote

2020/05/07に投票開始、2020/05/21に投票終了。
受理には2/3+1の賛成が必要です。

Patches and Tests

GitHub Pull request #5313

References

PHP RFC: Reserve Even More Types in PHP 7
phpDocumentor type reference

感想

これ、UNION型のRFCの将来の展望にある型宣言そのものですよね。
ついでだから型宣言自体もできるようにしてしまえばいいのでは。

ということでPHP8からmixed型が使えるようになります。

var_dumpのような、仕様としてあらゆる値を受け取る必要のある関数のためにこれが必要なことは確かでしょう。

しかし、ユーザランドで何も考えず適当にこれを使って大惨事、という未来が目に見えますね。
まあでも、そんな人はそもそも型引数を書かないだろうから問題ないかな?

これがなくては生きていけないVS Codeエクステンション10選

VisualStudioCodeは大人気なだけあって、有能なエクステンションが次から次へと出てきますね。
色々とっかえひっかえ試して遊んでいる人も多いのではないでしょうか。
なんかMinecraftのMOD環境構築と同じ空気を感じますね。
(環境ができたところで飽きて遊ばなくなる)

まあ一番手っ取り早いのは、既に評価の固まっている手堅いエクステンションを導入することです。
ということで以下はSahil Bondre( Twitter / GitHub / LinkedIn / Instagram / 個人HP )による記事、💡 10 VS Code extensions I can't code withoutの日本語訳です。

💡 10 VS Code extensions I can't code without

VSCodeは私のお気に入りのエディタです。
存在するコードエディタの中でも最も拡張性の高く、人気のあるエディタです。
そして驚いたことに、これはMicrosoftが作っています。
個人的には、他のエディタはVSCodeの機能の半分にも達しているものはないと感じています。
VSCodeをこれほど確固たる地位に押し上げた理由が、そのエクステンションのシステムです。
これにより、おおよそあらゆるユースケースに対応した拡張を書くことができます。
以下で私的エクステンション、トップ10を紹介します。

1. Beautify

ext install HookyQR.beautify

HTML、JS、CSS、JSON、そしてSASSを自分好みのスタイルでフォーマットすることができます。
内部のjs-beautifyを拡張して、自分だけのスタイルにカスタマイズすることもできます。

2. Better Comments

02.png

ext install aaron-bond.better-comments

コメントにセマンティクスを追加することができます。
コメントをアラート、クエリ、TODO、ハイライトのように分類してくれます。

021.png

3. Bookmarks

03.png

何百行ものコードの中で迷子になってしまったときに、このエクステンションが救世主となるでしょう。
コードの任意の行をブックマークすることができます。
ブックマークの一覧から選択するだけで、該当の場所に戻ってくることができます。

031.png

4. Bracket Pair Colorizer 2

04.png

ext install CoenraadS.bracket-pair-colorizer-2

この機能は、その名のとおりです。
括弧を多重に使用した際にペアごとに色分けします。
コードの奥深くに幾つものスコープがあるときに役立つでしょう。

041.png

5. Format in Context Menus

05.png

ext install lacroixdavid1.vscode-format-context-menu

サイドバーから選択するだけで、全てのファイルをフォーマットすることができます。
大量のファイルがあり、そしてあなたの環境がFormatterやLinterをサポートしていない場合に特に有用です。

051.gif

6. Git Graph

06.png

ext install mhutchie.git-graph

リポジトリのGitグラフを表示し、グラフからGitアクションを素早く実行することができます。
高度な設定が可能で、多くの機能を備えています。
このエクステンションの素晴らしさを布教するため、私はきっとまた別の記事を書くことになるでしょう。

061.gif

7. Git Lens

07.png

ext install eamodio.gitlens

GitLensは、blameとCodeLensによってコードのオーサリングを手助けしてくれます。
Gitリポジトリをシームレスにナビゲートしてエクスクローラーしたり、有能な比較コマンドを用いて知見を得たり、その他多くのことが可能です。

071.png

8. indent-rainbow

08.png

ext install oderwat.indent-rainbow

コードのインデントを深さごとに着色してくれます。
これは深いインデントのあるPythonのような言語で特に有用です。

9. Path Intellisense

09.png

ext install christian-kohler.path-intellisense

ファイルパスを自動補完してくれるようになります。
VSCodeはデフォルトでも自動補完をサポートしてますが、それはHTML/CSS/JavaScriptファイルに限定されています。
このエクステンションであらゆる言語とファイルタイプに拡張されます。

091.gif

10. Total Lines

10.png

ext install praveencrony.total-lines

ステータスバーに現在開いているファイルの行数を表示するだけの、小さなエクステンションです。
あなたのエクステンションのコレクションに追加されるべき良いものです。

これが私のお気に入りVSCodeエクステンションです。
あなたのお気に入り拡張機能をコメントでおしえてくれませんか?
ところでBashを学びたくありませんか?
シェルスクリプトのいいクラッシュコースありますよ

🌟 いくつかチートシート作ってるよ。
🚀 Instagram | Github | Twitter | Websiteでストーキングしてくれていいのよ。
😄 素敵な一日を過ごせますように!

コメント欄

「VSC半年使ってたのにBookmarks知らんかった。いったいどれだけ時間を無駄にしたのだろう。」「Codeletsでブックマーク。こいつはいいぞ最高だぞ。」
「Bookmarksさっそくインストールして、コードを後で参考にするために使ってる。」
「Bracket pair colorizer気に入った。ありがとう。」
「未知のエクステンションばっかりだった、さっそく試してみる。」
「HTML、CSS、JS、Java、Node、Ruby、他いろいろエクステンションを入れてたらあまりに巨大になりすぎた。どうにかしなければならないのではないか。」「対象に合わせて拡張パックを作って、それらの有効/無効を切り替えるってやってる。」「それはいいことを聞いた。」「よかったら私の拡張パックを紹介するよ。」

感想

私の環境では既に半分インストール済でした。
星の数ほどエクステンションがあるとは言っても、やはり汎用的に有用なものとなるとある程度数は限られてきますね。

コメント欄ではBookmarksがやたら高評価でした。
気軽に多くの付箋を置いておいて、後からすぐにそこに戻れるので、なかなか便利です。
私のように見終わったタブをついうっかり閉じてしまう癖のある人には、より有用だと思われます。

あとGit Graphも入れてなかったので入れてみたのですが、これ便利だな。
これまでわざわざ別ウィンドウでTortoiseGitなりGit bashなりを立ち上げていたのですが、VSCode上でGitツリーを表示して、ツリー上で各種操作を簡単に行うことができます。
これは使い方を調べてみる価値がありそうです。

とまあこんなかんじでエクステンションを入れまくっていたら、今何が入っててバージョンがどうなってるとかの管理がさっぱりになります。
既になっています。
言語や開発対象ごとに有効無効を切り替えたいときもあるでしょう。
そんなときのためにエクステンションを管理するエクステンションがほしいところですね。

【PHP8.0】例外をcatchしたいけど何もしたくない

例外をcatchしたいけど何もしたくない。

try{foo();}catch(Throwable$e){// 何もしない}

何もしないのにわざわざ変数に受け取るのって無駄じゃありませんか?

というわけでnon-capturing catchesというRFCが提出されました。

PHP RFC: non-capturing catches

Introduction

今のところ、PHPは例外を明示的に変数でキャプチャする必要があります。

try{foo();}catch(SomeException$ex){die($ex->getMessage());}

しかしながら、ときにはその変数を使わない場合もあります。

try{changeImportantData();}catch(PermissionException$ex){echo"You don't have permission to do this";}

これを見た人は、プログラマが意図してこの変数を使わなかったのか、あるいはバグなのかがわかりません。

Proposal

例外を変数に受け取らずcatchできるようにします。

try{changeImportantData();}catch(PermissionException){// catchした変数を使わないという意図が明白echo"You don't have permission to do this";}

Prior art

7年前にあった似たようなRFCは、以下のように例外名も省略可能にするという理由で否定意見が多数でした。

try{foo();}catch{bar();}

いっぽう、このRFCは肯定的な反応が多く、再検討の余地があります。

Backward Incompatible Changes

後方互換性を壊す変更はありません。

Proposed PHP Version(s)

PHP8.0

RFC Impact

特になし。

Vote

投票は2020/05/24まで、投票者の2/3の賛成で受理されます。
このRFCは賛成48反対1の圧倒的多数で受理されました。

Patches and Tests

https://github.com/php/php-src/pull/5345

References

メーリングリスト

感想

このRFCが意図しているところは決して例外の握り潰しなどではなく、例外処理のロジックに例外の中身を使わないときに省略できるというものです。
変数に受け取る処理がなくなるので速度も速まることでしょう。

しかしですな、こんな機能があったら絶対にこんなコードを書く奴が出てくるわけですよ。

PHP8
try{foo();}catch(Exception){}

いやまあ、こんなの書いてしまう人は今でもやってると思いますけどね。

PHP7
try{foo();}catch(Exception$e){}

なら別にあってもいいか。

劇的に便利になるというわけではないですが、ちょっと気が利く書き方ができるようになりますね。

最後にもう一度言いますが、冒頭のコードは悪い例なので決して真似してはいけません。

【PHP8.0】PHPにオブジェクト初期化子が導入される

これまで何度も塩漬けにされたり却下されたりしていたオブジェクト初期化子ですが、ついにPHP8.0で導入されることになりました。
オブジェクト初期化子が何かというとこれです。

classHOGE{publicfunction__construct(privateint$x){// $HOGE->xが生える}}

これはオブジェクト初期化子でいいのか?

日本語で何と表すのか適切な単語が思いつかなかったのでとりあえずオブジェクト初期化子としておきます。
愚直に訳すと"コンストラクタ引数昇格"ですが、そんな単語は無いうえに型昇格と紛らわしいです。
引数プロパティ宣言パラメータプロパティ宣言もほぼ使われてないし何と表現すればいいのだろう。
きっと誰かが適切な語をプルリクしてくれるはず。

以下は該当のRFC、PHP RFC: Constructor Property Promotionの日本語訳です。

PHP RFC: Constructor Property Promotion

Introduction

PHPでは現在のところ、オブジェクトにプロパティを定義するだけでも同じことを複数回書かなければならないため、多くの無駄が必要です。
以下の単純なクラスを考えてみましょう。

classPoint{publicfloat$x;publicfloat$y;publicfloat$z;publicfunction__construct(float$x=0.0,float$y=0.0,float$z=0.0,){$this->x=$x;$this->y=$y;$this->z=$z;}}

プロパティの表記は、1:プロパティの宣言、2:コンストラクタの引数、3:プロパティの代入で3回も繰り返されます。
さらにプロパティの型も2箇所に書かなければなりません。

プロパティ宣言とコンストラクタ以外には何も含まれていないバリューオブジェクトでは特に、多くの重複によって変更が複雑になり、エラーを起こしやすいものとなります。

このRFCでは、プロパティの定義とコンストラクタを組み合わせるショートハンド構文の導入を提案します。

PHP8
classPoint{publicfunction__construct(publicfloat$x=0.0,publicfloat$y=0.0,publicfloat$z=0.0,){}}

このショートハンド構文は、前述の例と厳密に同じで、より短く書くことができます。
構文は姉妹言語Hackから採用しています。

Proposal

コンストラクタの引数にpublic/protected/private何れかが記述されている場合、その引数は"promoteされた引数"とします。
promoteされた引数には、同じ名前のプロパティが追加され、値が割り当てられます。

Constraints

promoteはabstractではないクラスのコンストラクタでのみ記述可能です。
従って、以下のような構文は使用不能です。

// エラー:コンストラクタではないfunctiontest(private$x){}abstractclassTest{// エラー:abstractなので駄目abstractpublicfunction__construct(private$x);}interfaceTest{// エラー:interfaceも駄目publicfunction__construct(private$x);}

一般的でない使い方ですが、トレイトでは使用可能です。

対応する可視性キーワードはpublic/protected/privateのみです。

classTest{// エラー:varはサポートしてないpublicfunction__construct(var$prop){}}

promoteされた引数によるプロパティは、通常のプロパティと全く同じ扱いになります。
特に注意点として、同じプロパティを二度宣言することはできません。

classTest{public$prop;// Error: Redeclaration of property.publicfunction__construct(public$prop){}}

また、プロパティにすることのできないcallable型は使用することができません。

classTest{// Error: Callable type not supported for properties.publicfunction__construct(publiccallable$callback){}}

promoteされたプロパティはプロパティ宣言と同義であるため、デフォルトがNULLの場合はNULL許容型を明示しなければなりません。

classTest{// Error: Using null default on non-nullable propertypublicfunction__construct(publicType$prop=null){}// こっちはOKpublicfunction__construct(public?Type$prop=null){}}

可変長引数をpromoteすることはできません。

classTest{// エラーpublicfunction__construct(publicstring...$strings){}}

理由としては、明示する引数の型(ここではstring)と、実際に渡される引数の型(ここではstringの配列)が異なるからです。
$stringsプロパティをstringの配列にすることも可能ですが、それではわかりづらくなります。

promoteプロパティと明示的なプロパティ宣言を組み合わせることは可能です。
またpromoteプロパティとpromoteされない引数を同時に渡すことも可能です。

// 正しいclassTest{publicstring$explicitProp;publicfunction__construct(publicint$promotedProp,int$normalArg){$this->explicitProp=(string)$normalArg;}}

Desugaring

promoteプロパティはただのシンタックスシュガーであり、全てのpromoteプロパティに対して以下の変換が適用されます。

// シンタックスシュガーclassTest{publicfunction__construct(publicType$prop=DEFAULT){}}// こう展開されるclassTest{publicType$prop;publicfunction__construct(Type$prop=DEFAULT){$this->prop=$prop;}}

自動的に宣言されるプロパティの可視性と型は、promoteプロパティの可視性および型と同じになります。
注目すべき点は、プロパティにデフォルト値は適用されず(つまり、未初期化で始まります)、コンストラクタ引数でのみ指定されるところです。

プロパティ宣言時にもデフォルト値を指定したほうがよいようにも思えますが、将来的にデフォルト値で指定することが望ましくなるであろう理由が存在します。

ひとつめは、プロパティのデフォルト値に任意の式を利用できるようにする拡張の可能性です。

// FROMclassTest{publicfunction__construct(publicDependency$prop=newDependency()){}}// TOclassTest{publicDependency$prop/* = new Dependency() */;publicfunction__construct(Dependency$prop=newDependency()){$this->prop=$prop;}}

こうなると、プロパティ宣言時とデフォルト値でオブジェクトを2回構築することとなるため望ましくありません。

また、新潟アクセス修正子のルールではプロパティでデフォルト値を宣言すると、コンストラクタで代入することもできなくなります。

promote引数が参照であった場合、プロパティも参照になります。

// FROMclassTest{publicfunction__construct(publicarray&$array){}}// TOclassTest{publicarray$array;publicfunction__construct(array&$array){$this->array=&$array;}}

promoteプロパティへの引数の割り当ては、コンストラクタの冒頭で行われます。
従って、コンストラクタ内でも引数とプロパティの両方にアクセスすることが可能です。

// 動作するclassPositivePoint{publicfunction__construct(publicfloat$x,publicfloat$y){assert($x>=0.0);assert($y>=0.0);}}// こっちも動作するclassPositivePoint{publicfunction__construct(publicfloat$x,publicfloat$y){assert($this->x>=0.0);assert($this->y>=0.0);}}

Reflection

リフレクションおよびその他の解析機構で見ると、シンタックスシュガーを解除した後の状態になります。
すなわち、promoteプロパティは明示的に宣言されたプロパティのように見え、promote引数は通常のコンストラクタ引数のように見える、ということです。

PHPは引数に関するDocコメントを公開していませんが、promoteプロパティのDocコメントも保持されます。

classTest{publicfunction__construct(/** @SomeAnnotation() */public$annotatedProperty){}}$rp=newReflectionProperty(Test::class,'annotatedProperty');echo$rp->getDocComment();// "/** @SomeAnnotation */"

この例のように、promoteプロパティではDocコメントベースのアノテーションを使用することができます。

また、2メソッドが追加されます。

ReflectionProperty::isPromoted()は、promoteプロパティであればtrueを返します。
ReflectionParameter::isPromoted()は、promote引数であればtrueを返します。

プロパティがpromoteされたかどうかを気にする場面はほとんど存在しないと思われますが、この情報によって元のコードをより簡単に再構築することができます。

Inheritance

オブジェクト初期化子は継承することができますが、特に特筆すべきようなことはありません。
abstrautを含む典型的な継承のユースケースを以下に示します。

abstractclassNode{publicfunction__construct(protectedLocation$startLoc=null,protectedLocation$endLoc=null,){}}classParamNodeextendsNode{publicfunction__construct(publicstring$name,publicExprNode$default=null,publicTypeNode$type=null,publicbool$byRef=false,publicbool$variadic=false,Location$startLoc=null,Location$endLoc=null,){parent::__construct($startLoc,$endLoc);}}

ParamNodeクラスでいくつかのpromoteプロパティを宣言し、さらに二つの普通の引数を親コンストラクタに転送しています。
これは以下のように展開されます。

abstractclassNode{protectedLocation$startLoc;protectedLocation$endLoc;publicfunction__construct(Location$startLoc=null,Location$endLoc=null,){$this->startLoc=$startLoc;$this->endLoc=$endLoc;}}classParamNodeextendsNode{publicstring$name;publicExprNode$default;publicTypeNode$type;publicbool$byRef;publicbool$variadic;publicfunction__construct(string$name,ExprNode$default=null,TypeNode$type=null,bool$byRef=false,bool$variadic=false,Location$startLoc=null,Location$endLoc=null,){$this->name=$name;$this->default=$default;$this->type=$type;$this->byRef=$byRef;$this->variadic=$variadic;parent::__construct($startLoc,$endLoc);}}

プロパティへの代入は、親コンストラクタが呼ばれる前に行われることに注意してください。
これはコーディングスタイルとして一般的ではありませんが、動作に影響が出るようなことはほぼありません。

Attributes

PHP8ではアトリビュートも導入されるため、相互作用を考慮する必要があります。
アトリビュートは、プロパティと引数の両方で使用することができます。

classTest{publicfunction__construct(<<ExampleAttribute>>publicint$prop,){}}

このコードがどのように解釈されるか決める必要があります。
1. アトリビュートは引数にのみ適用する。
2. アトリビュートはプロパティにのみ適用する。
3. アトリビュートは引数とプロパティの両方に適用する。
4. 曖昧さを避けるためエラーにする

// Option 1: アトリビュートは引数にのみ適用するclassTest{publicint$prop;publicfunction__construct(<<ExampleAttribute>>int$prop,){}}// Option 2: アトリビュートはプロパティにのみ適用するclassTest{<<ExampleAttribute>>publicint$prop;publicfunction__construct(int$prop,){}}// Option 3: アトリビュートは引数とプロパティの両方に適用するclassTest{<<ExampleAttribute>>publicint$prop;publicfunction__construct(<<ExampleAttribute>>int$prop,){}}// Option 4: 曖昧さを避けるためエラーにする

このRFCでは3番目、つまり引数とプロパティの両方に適用することを提案しています。
これが最も柔軟性の高い方法だからです。

ただし、これは実装に依ると考えています。
PHP8の実装に関わる作業で、アトリビュートをプロパティにのみ配置した方がよいと判明した場合は、そのように変更される場合があります。

Coding Style Consideration

このセクションではコーディングスタイルの推奨について解説します。
規程ではありません。

promoteプロパティを使用する場合、コンストラクタをクラス最初のメソッドとして、明示的なプロパティ宣言の直後に配置することをお勧めします。
これにより、全ての全てのプロパティが先頭にまとめられ、一目でわかるようになります。
静的メソッドを最初に配置することを要求しているコーディング規約は、コンストラクタを最初に配置するよう規約を調整する必要があります。

promoteプロパティに@paramアノテーションを使用している場合、ドキュメントツールは@varアノテーションも含まれているものとして解釈されるべきです。

// 元のコードclassPoint{/**
     * Create a 3D point.
     *
     * @param float $x The X coordinate.
     * @param float $y The Y coordinate.
     * @param float $z The Z coordinate.
     */publicfunction__construct(publicfloat$x=0.0,publicfloat$y=0.0,publicfloat$z=0.0,){}}// こう解釈するclassPoint{/**
     * @var float $x The X coordinate.
     */publicfloat$x;/**
     * @var float $y The Y coordinate.
     */publicfloat$y;/**
     * @var float $z The Z coordinate.
     */publicfloat$z;/**
     * Create a 3D point.
     *
     * @param float $x The X coordinate.
     * @param float $y The Y coordinate.
     * @param float $z The Z coordinate.
     */publicfunction__construct(float$x=0.0,float$y=0.0,float$z=0.0,){$this->x=$x;$this->y=$y;$this->z=$z;}}

最後に、promoteプロパティは、あくまで一般的なケースをカバーするための便利な省略記法であるに過ぎないことに注意してください。
promoteプロパティはいつでも明示的なプロパティに書き換えることができます。
そのため、この変更は下位互換性を壊すことはありません。

Backward Incompatible Changes

下位互換性のない変更はありません。

Future Scope

Larryが、この機能と他の機能を組み合わせることによってオブジェクト初期化を改善する方法について、より深いビジョンを提供しています

Prior Art

この機能、あるいは類似した機能は多くの言語でサポートされています。
Hack
TypeScript
Kotlin

先行するRFCが存在します。
Automatic property initialization プロパティ宣言は必要とする、より弱い形です。
Constructor Argument Promotion このRFCとほとんど同じです。
Code free constructor Kotlinの文法に基づいています。

Vote

投票期間は2020/05/29まで、2/3+1の賛成が必要です。
このRFCは賛成46反対10で受理されました。

感想

プロパティを書くのが格段に楽になりますね。
後から見るときにプロパティが宣言されているのかどうかちょっとわかりにくそうですが、この機能が使えるのはコンストラクタだけなのでそこだけ抑えていれば大丈夫でしょう。
コンストラクタだけではなく任意のメソッドで使えると便利では、と一瞬思ったものの、これを無制限に使えると完全に収拾が付かなくなってしまうので、やはりコンストラクタだけに留めておくのが賢明そうですね。

YEStifications:Googleの中の人が語る、通知許可プロンプトに関するCrUXの統計レポート

以下は、Googleの中の人であるRick Viscomi( Twitter / GitHub )による記事、YEStifications: Exploring how users engage with notification prompts in the Chrome UX Reportの日本語訳です。

YEStifications: Exploring how users engage with notification prompts in the Chrome UX Report

📢 example.com wants to show notifications

Notifications APIはユーザを留めるための効果的な方法ですが、実のところ多くのサイトはユーザへの通知を許可してもらえていません。
よくある問題は、ページが読み込まれると即座に、あるいはどのように使用されるかの説明なしに、いきなり許可を求めるプロンプトを表示することです。
では、ユーザは通知許可プロンプトにどのように反応しているでしょうか。
CrUXの出番です。

Chrome UX Report、通称CrUXには、3万を超えるWebサイトに対して、通知許可プロンプトに対するユーザの反応のデータが存在します。
このデータは、パブリックBigQueryリポジトリで公開されています。
ユーザは通知許可プロンプトにどのように反応しているか、この記事で見ていきましょう。

The state of notification permissions

手始めに、このデータセットに含まれているWebサイトの総数を数えてみましょう。

SELECTCOUNT(DISTINCTorigin)FROM`chrome-ux-report.all.202001`WHEREexperimental.permission.notificationsISNOTNULL

結果は31515で、通知のパーミッションを含むoriginが31515件あることを示しています。
originはWebサイトの最初の/までのアドレスのことで、たとえばhttps://www.example.comです。
ひとつのWebサイト上でのユーザの挙動は、全てorigin単位にロールアップされています。

次は試しに、SlackのWebサイトについて通知のパーミッションを見てみましょう。

SELECTSUM(experimental.permission.notifications.accept)ASaccept,SUM(experimental.permission.notifications.dismiss)ASdismiss,SUM(experimental.permission.notifications.deny)ASdeny,SUM(experimental.permission.notifications.ignore)AS`ignore`FROM`chrome-ux-report.all.202001`WHEREorigin='https://app.slack.com'
AcceptDismissDenyIgnore
92.10%5.67%1.96%0.28%

01.png

見てのとおり、Slackアプリに対しての通知の許可承諾率は92%と、非常に優れています。
言い換えると、通知許可プロンプトが表示されたときに、92%のユーザは許可を与えることを選択したということです。
残りユーザのうち約6%は許可か拒否かを選択することなくプロンプトを閉じ、2%は明示的に拒否しました。
そして、プロンプトに何もせず放置したユーザは約0%です。

ところで、Slackに対する通知パーミッションの結果は、Web全体の通知パーミッションの結果を端的に表していると言えるでしょうか?
調査対象を3万サイト全てに拡大してみましょう。

SELECTorigin,SUM(experimental.permission.notifications.accept)ASaccept,SUM(experimental.permission.notifications.dismiss)ASdismiss,SUM(experimental.permission.notifications.deny)ASdeny,SUM(experimental.permission.notifications.ignore)AS`ignore`FROM`chrome-ux-report.all.202001`WHEREexperimental.permission.notificationsISNOTNULLGROUPBYoriginORDERBY`ignore`ASC,acceptDESC

02.png

これは何を表しているでしょうか。
チャート右上のグレー部分は、通知許可プロンプトを無視したユーザの割合を示しています。
次に、チャート中央の最もスペースを取っている赤い領域は、通知許可プロンプトを明示的に拒否した割合です。
そして、チャート左端の急減している緑色の領域が、通知許可プロンプトを許可したユーザの割合です。
最後に、許可と拒否の間にある黄色が、許可も拒否も選択せずに通知許可プロンプトを閉じた割合です。

この図から、いくつかの推論を得られそうです。

最もわかりやすいのは、チャートの結果は赤が大多数であり、緑は少ないということです。
大半のユーザは通知許可プロンプトを拒否しています。
すなわち、Slackは極端に許可率の高い例外中の例外と言えそうです。

また、拒否率と無視率には逆相関がありそうです。
拒否率が小さくなると、無視率が高くなるようです。

Slackが明らかに例外であることはわかりましたが、それでは平均的なWebサイトでの割合はどのようなものでしょうか。

SELECTAVG(accept/total)ASaccept,AVG(dismiss/total)ASdismiss,AVG(deny/total)ASdeny,AVG(`ignore`/total)AS`ignore`FROM(SELECTSUM(experimental.permission.notifications.accept)ASaccept,SUM(experimental.permission.notifications.deny)ASdeny,SUM(experimental.permission.notifications.ignore)AS`ignore`,SUM(experimental.permission.notifications.dismiss)ASdismiss,SUM(experimental.permission.notifications.accept+experimental.permission.notifications.deny+experimental.permission.notifications.ignore+experimental.permission.notifications.dismiss)AStotalFROM`chrome-ux-report.all.202001`WHEREexperimental.permission.notificationsISNOTNULL)
AcceptDismissDenyIgnore
16.71%23.84%40.74%18.71%

03.png

これが現実的な通知許可の割合です。
ただし、ユーザ数が全く異なるWebサイトのUXを平均化したものであることに注意してください。
この結果によると、平均的なWebサイトでは通知許可プロンプトを承認するユーザは17%であり、24%は選択せずに通知許可プロンプトを閉じ、41%が明示的に拒否し、19%が無視するということになりました。

このデータセットから他に何かわかることがないでしょうか?
今度はデバイスのフォームファクタごとに分けてみましょう。

SELECTdevice,AVG(accept/total)ASaccept,AVG(dismiss/total)ASdismiss,AVG(deny/total)ASdeny,AVG(`ignore`/total)AS`ignore`FROM(SELECTform_factor.nameASdevice,SUM(experimental.permission.notifications.accept)ASaccept,SUM(experimental.permission.notifications.deny)ASdeny,SUM(experimental.permission.notifications.ignore)AS`ignore`,SUM(experimental.permission.notifications.dismiss)ASdismiss,SUM(experimental.permission.notifications.accept+experimental.permission.notifications.deny+experimental.permission.notifications.ignore+experimental.permission.notifications.dismiss)AStotalFROM`chrome-ux-report.all.202001`WHEREexperimental.permission.notificationsISNOTNULLGROUPBYdevice)GROUPBYdeviceORDERBYacceptDESC
DeviceAcceptDismissDenyIgnore
スマホ22.76%18.03%57.33%1.88%
タブレット18.75%20.24%58.34%2.67%
PC6.11%34.01%11.61%48.27%

04.png

明白な違いが現れました。
スマホとデスクトップでは、露骨に大きな差があります。
スマホユーザは通知許可プロンプトを無視する割合が非常に低いのに対して、デスクトップユーザは約半数が通知許可プロンプトを無視します。
最も理解しやすい理由は画面サイズでしょう。
通知許可プロンプトが画面の大半を占めている場合、それを無視し続けることは難しいことです。
また興味深いこととしては、スマホユーザは拒否率が57%と非常に高いのに、承認率も23%であり最も高いということです。
理由のひとつとしては、スマホは常に持ち歩くので、通知を受信するには最も自然で便利なフォームファクタであるということがあるかもしれません。

Where we go from here

ユーザは明らかに、押し付けがましい通知許可プロンプトを鬱陶しく感じており、そして開発者もそのことを風の噂に聞いたり実際に経験したりしているかもしれません。
今回我々は、Web全体で通知許可プロンプトがどのように位置付けされているかの実用的なデータを手に入れました。
CrUXのデータから明らかなのは、ユーザに通知許可プロンプトを効果的に承認させることができているサイトが非常に少ないということです。

Chrome開発チームは、ユーザのフラストレーションを減らすために通知許可プロンプトの表示方法変更を発表しました。
通知許可プロンプトの承認率が低いサイトは、より目立たない通知許可プロンプトが自動的に表示されるようになります。
そのため、サイトオーナーは通知許可プロンプトの承認率に注意を払う必要があります。

サイトで通知の許可要求を出しているのであれば、要求に対する反応の割合を調べるためのアクセス解析を追加しましょう。
CrUXデータセットはアクセス解析にはなりませんが、類似したサイトの承認率がどうなっているか調べたり、Web全体のトレンドを調べたりすることに役立ちます。
承認率を向上させ、より多くの効果を得るためには、通知許可プロンプトのベストプラクティスを読むとよいでしょう。

この記事で紹介したクエリは、BigQueryで実行することができます。
クエリを改良して、独自の視点を見つけ出してみるのもよいでしょう。
より詳しい使い方はusing CrUX on BigQueryを参照してください。
データに関する質問やフィードバックがある場合は、以下のいずれかのチャンネルからCrUXチームに連絡をお待ちしています。

chrome-ux-report Google Groups
@ChromeUXReport Twitter
chrome-ux-report StackOverflow
GoogleChrome/CrUX GitHub

コメント欄

「通知を求めるサイトの多さに辟易し、他のみんなは好きなのかと考えていた。Slackのように意味のあるサイト以外ではみんな好きではないようだ。」
「PCでの無視率が大きいのは画面が大きいからなのか、マルチタスクのせいなのか、他に理由があるのか、なんだろう。」
「Skackでの通知は明白なユースケースがある。タブロイド紙とかのユースケースが全く異なるサイトと一緒くたにすると有益な結果が得られないかもしれない。」
「対話的な通知許可プロンプトを出してるサイトとの許可率のちがいを出してほしい。」
「最初にページを表示してから通知許可プロンプトを出すまでの時間でプロットしてみると面白いかも。」「ページロード時に通知許可プロンプトを出しているかはチェックしてるから、今後そのネタで記事を書くかも。」

感想

むしろ2割も承認してることにびっくりだよ。
0.1%くらいかと思ってたよ。

というわけで、何も考えずに通知許可プロンプトを出すと、ほとんどの人が拒否します。
これは想像でも妄想でもなく、歴然とした事実です。
こんなの言うまでもなく考えるでもなく当然なわけですが、そのあたりを何も考えていないな記事やサービスが溢れた結果、当然の帰結としてWebPUSH通知は邪悪なものに成り果てました。
ま、そもそもServiceWorker自体が邪悪な用途にしか使われない技術なので今更ですけどね。

そんなわけで2019年11月、Firefoxはユーザインタラクションなしに通知許可ウィンドウを出すことを禁止しました。
Chromeはこの手の変更には極めて消極的なのでまだ追随していませんが( せいぜい目立たなくした程度 )、いずれは同様の変更が入るでしょう。

この記事の著者はGoogleの中の人なので通知許可プロンプトそのものには否定的ではなく、使うときはうまく導線を考えようという立場です。
全てのWebサイトが適切に通知を実装すれば、それは確かに有益で便利な機能となることでしょう。
しかし、大多数のWebサイトがそのようなガイドラインに従うかというとそんなわけがありません。
従って、ユーザ側として最も適切な設定はデフォルトで一律禁止ということになります。
これだけで、いちいち煩わしい通知許可プロンプトに邪魔されることがなくなり、快適なブラウジングを行うことができるようになります。
Slackなど通知が必要になったらそのときに個別に設定すればいいだけですし、そして通知が必要なサイトはほとんどありません。

【PHP8】厳密なswitch文ことmatch式が使えるようになる

PHPがよく言われる問題点のひとつとして、switch曖昧な比較であるということが挙げられます。

switch($x){case1:'$xは1だよ';break;case"1":'$xは"1"だよ';break;}

case "1"に到達することは決してありません。

ということで厳密な比較を用いるswitchことmatch構文のRFCが提出されました。
以下はMatch expression v2の日本語訳です。

PHP RFC: Match expression v2

Proposal

このRFCは、switchに似ていますが、より安全なセマンティクスを持つmatch構文の提案です。

例として、Doctrineのクエリパーサを挙げます。

// Beforeswitch($this->lexer->lookahead['type']){caseLexer::T_SELECT:$statement=$this->SelectStatement();break;caseLexer::T_UPDATE:$statement=$this->UpdateStatement();break;caseLexer::T_DELETE:$statement=$this->DeleteStatement();break;default:$this->syntaxError('SELECT, UPDATE or DELETE');break;}// After$statement=match($this->lexer->lookahead['type']){Lexer::T_SELECT=>$this->SelectStatement(),Lexer::T_UPDATE=>$this->UpdateStatement(),Lexer::T_DELETE=>$this->DeleteStatement(),default=>$this->syntaxError('SELECT, UPDATE or DELETE'),};

Differences to switch

Return value

後で使いたい値をswitchで生成することは非常によくあることです。

switch(1){case0:$result='Foo';break;case1:$result='Bar';break;case2:$result='Baz';break;}echo$result;//> Bar

そして$resultに代入し忘れることもよくあるミスです。
さらに深くネストされていた場合は、$resultがしっかり代入されているか確認するのもたいへんです。
それに対し、matchは実行した結果が評価される式です。
これによって多くの定型文を削除することができ、代入忘れというミスがなくなります。

echomatch(1){0=>'Foo',1=>'Bar',2=>'Baz',};//> Bar

No type coercion

switch文は緩やかな比較==を使います。
これは直感に反する結果をもたらすことがあります。

switch('foo'){case0:$result="Oh no!\n";break;case'foo':$result="This is what I expected\n";break;}echo$result;//> Oh no!

match式は厳密な比較===で比較します。
strict_typesの設定に関わらず常に厳密です。

echomatch('foo'){0=>"Oh no!\n",'foo'=>"This is what I expected\n",};//> This is what I expected

No fallthrough

switchフォールスルーは、多くの言語でバグの温床となっています。
caseは明示的にbreakしないかぎり、次のcaseへと実行が継続されます。

switch($pressedKey){caseKey::RETURN_:save();// break忘れたcaseKey::DELETE:delete();break;}

match式では、暗黙のbreakを付与することで、この問題を解決します。

match($pressedKey){Key::RETURN_=>save(),Key::DELETE=>delete(),};

複数条件で同じコードを実行したい場合は、条件をカンマで区切ります。

echomatch($x){1,2=>'Same for 1 and 2',3,4=>'Same for 3 and 4',};

Exhaustiveness

switchでよくあるもうひとつの問題は、全てのcaseに対応していない場合の処理です。

switch($operator){caseBinaryOperator::ADD:$result=$lhs+$rhs;break;}// BinaryOperator::SUBTRACTを渡しても何も起こらない

これが原因で、よくわからないところでクラッシュしたり、想定していない動作をしたり、なお悪いときにはセキュリティホールの原因になったりします。

$result=match($operator){BinaryOperator::ADD=>$lhs+$rhs,};// BinaryOperator::SUBTRACTを渡すと例外が発生する

match式はどのcaseにも当てはまらなかった場合はUnhandledMatchErrorを発するので、間違いに早期に気付くことができます。

Miscellaneous

Arbitrary expressions

matchする条件を任意の式にすることができます。
比較条件はswitch同様上から順に判定され、マッチした以後の条件は評価されません。

$result=match($x){foo()=>...,$this->bar()=>...,// foo()がマッチしたらここは呼ばれない$this->baz=>...,// etc.};

Future scope

この項目は将来の予定であり、このRFCには含まれません。

Blocks

このRFCでは、match式の本文はひとつの式でなければなりません。
ブロックを許すかについては、別のRFCで議論します。

Pattern matching

パターンマッチングについても検討しましたが、このRFCには含めないことにしました。
パターンマッチングは非常に複雑であり、多くの調査が必要です。
パターンマッチングについては別のRFCで議論します。

Allow dropping (true)

$result=match{...};// ↓と同じ$result=match(true){...};

Backward Incompatible Changes

matchがキーワードreserved_non_modifiersとして追加されます。
以下のコンテキストで使用することができなくなります。
・名前空間
・クラス名
・関数名
・グローバル定数

メソッド名およびクラス定数としては引き続き使用可能です。

Syntax comparison

他言語でのmatch構文

Vote

投票は2020/07/03まで、投票者の2/3の賛成で受理されます。
2020/06/22時点では賛成20反対1となっていて、よほどの問題でも発生しないかぎり受理されるでしょう。

感想

switchでよく問題になっていた曖昧な比較やbreakし忘れといったミスが、構文レベルで不可能となります。
そのため、match式に従っておけばswitchに起因する問題はほぼ発生しなくなるでしょう。
またmatch全体が返り値を持ってるのも便利ですね。

そのかわり、case内部には1式しか書けないため、複数の変数値を変更したり入れ子にしたりといった複雑な処理を書くことは難しくなります。
また、あえてbreakを書かずに継続したい場合も面倒な書き方になります。

// 1ならfooとbarを、2ならbarだけ実行したいswitch($x){case1:foo();case2:bar();break;}// aftermatch($x){1=>foo()&&bar(),// これは可能?2=>bar(),};

{}で括って複数の文を書けるようにするかどうかは、アロー関数同様今後の課題となっています。

従って本RFCは、決してあらゆるswitchを置き換える構文ではなく、アロー関数のように一部のswitch文を置き換えることができる短縮構文という立ち位置になります。
しかし、よほど変なことでもしていないかぎり、大抵のswitch文はmatch式に置き換えることができると思います。
安全性のためにも、今後はできるだけmatch式を使っていくとよいでしょう。

歴史上もっともお勧めのスタートアップ本 25選

Original article:https://www.daolf.com/posts/best-startup-books/

みなさんスタートアップやってますか?
私はやってません。
はて、スタートアップといっても何をどうすればいいのでしょうか。

ブロガーやその他の人々がお勧めしている本を読めば間違いないでしょう。きっと。
世界に羽ばたくためにも、世界中で読まれているスタートアップ本を読めばいいに違いありません。

ということで以下はPierre de Wulf( Twitter / GitHub / Webサイト )による記事、The 25 most recommended startup books of all timeの日本語訳です。

なお、Amazonへのリンクは元記事のままであり、和訳にあたり変更などはしていません。

The 25 most recommended startup books of all time

インターネット上には、これがスタートアップのための必読書の決定版であると主張するリストが無数に存在します。
そしてそれらのリストを見比べてみると、ほとんどが同じであり一部の本だけが異なっているように見えました。

スタートアップについて本当に最も推奨される本は何なのかを知りたかったので、私はこれを作りました。
インターネット上で見つけた、208以上のリストと、4000ほどのお勧めを集計しました。
私の知る限り、このリストが、この種のテーマに関する最も完全なリストです。

Disclaimer:この記事を作るのに数えきれないほどの時間を費やしたので、その時間を費やすことが有益な収益源になり得るか確認するため、Amazonのアフィリエイトリンクをはっています。あるいはならないか🤷‍♂️.

さっさと結果を見たいのであればこちらから結果に飛んでください。
集計に使った方法を知りたいというなら、少々お時間をください。

Methodology

単純に、Googleに"ベストなスタートアップの本"、"ベストな起業家の本"といったクエリを幾つか尋ね、そしてその後それら全てのページを集計しました。
この作業にはScrapingBeeというスクレイピングAPIを使用しています。

重複などを除外しても300近くのリストが残りました。
さらにタイトルから一目で不要とわかるリストを削除しました。

・特定の著者だけをまとめている
・特定のトピックに焦点を当てている(例:暗号起業家の為のベスト本)
・無料本のリスト
・QuoraやRedditのコピー

最終的に254個のHTMLファイルが残りました。
ブラウザで全てのファイルを開いて、Chromeインスペクタを開いて、記事中の書籍のタイトルに一致するセレクタを探して、タイトルを収集しました。
この作業には2時間、1ファイルあたり30秒ほどかかりました。

そして、収集したHTMLページからCSSを抽出するための大きなJSONファイルができあがりました。

x1.png

PythonとBeautiful soupを使って、CSSセレクタにマッチしたDOM要素内のテキストを抽出しました。
結果膨大な本のリストができあがり、後処理をしないと使い物になりませんでした。

x2.png

最も頻繁に引用されたスタートアップの本を見つける為には、結果をノーマライズする必要がありました。
たとえば"7 habits of highly effective people"という本は、"Seven habits of highly effective people"とか"7 habits for highly for effective people"といった微妙に異なるタイトルで別々に集計されていました。

さらに加えて、"{書名} by {著者}"や"{書名} - {著者}"のような書式のぶれが厄介でした。

最終的に、以下のようなシンプルなPythonで処理しました。

defclean_link(link):link=link.encode().decode('ascii',errors='ignore')link=link.replace("'",'')link=link.lower()link=' '.join([wforwinlink.split(' ')ifwnotin['the','a']])link=link.split('by')[0]link=link.split(':')[0]link=link.split('(')[0]link=' '.join(link.split())link=link.replace('-','_')link=''.join([cforcinlinkifc.isalpha()orc=='_'orc==' '])link=link.strip()link=link.replace(' ','_')link=''.join([cforcinlinkifc.isalpha()orc=='_'])returnlink

その後は手動で整形しました。

そして、最終的に以下のようなリストが完成しました。

x3.png

これでようやく、最もお勧めの本を計算できるようになりました。
全ての結果はこのリポジトリで確認することができます。
それでは集計結果を見ていきましょう。

The 25 most recommended startup books of all-time.

25. Delivering Happiness: A Path to Profits, Passion, and Purpose by Tony Hsieh (7.6% recommended)

25.jpg

ZapposのCEOであるTony Hsiehが、ビジネスと人生で学んだ様々な教訓を語ります。
LinkExchangeやZapposなどを渡り歩き、ミミズの養殖から始まりピザビジネスの運営まで、様々なことが書かれています。
速いペースで地に足の付いた文章で、企業文化の大きな違いが成功を収めるための強力なモデルであることを示しています。
あなたの周りの人々を幸せにすることが、あなた自身をも大きく幸せにしてくれます。

24. Shoe Dog: A Memoir by the Creator of Nike by Phil Knight (7.6% recommended)

24.jpg

Knightが最初の勝利を収めるまで、彼と彼の夢の間に立ちはだかった多くのリスクと困難と挫折を詳細に語っています。
社会不適合者と求道者の寄せ集めが、固い絆で結ばれた兄弟になるまでの、初めてのパートナーや社員との関係を振り返っています。
共通の使命とスポーツに対しての深い信念を共にした彼らは、全てを変えるブランドを築き上げました。

Phil KnightはNikeの共同創業者。

23. Purple Cow: Transform Your Business by Being Remarkable by Seth Godin (8.1% recommended)

23.jpg

あなたは紫の牛であるか、そうでないかのいずれかです。
あなたは目立っているか、誰にも気付かれないかのいずれかです。
どちらかを選んでください。
Apple、Starbucks、Dyson、そしてPret a Mangerの共通点は何でしょう?
彼らはどのようにして盛大に成長し、偉大なブランドになる最後の一歩を踏み出したのでしょうか、
価格(Pricing)、販促(Promotion)、宣伝(Publicity)といった、かつてマーケティングに使われていたPたちは、もはや機能していません。
広告の黄金時代は終わりました。
今やここに、新しいP…紫の牛(Purple Cow)を導入すべき時期なのです。
"Purple Cow"はとは驚くべき何か、直観に反する、刺激的で信じられない何か、を表します。
Seth Godinは、あなたが作る全てのもの、あなたが行う全てのことに、本当に注目すべき何らかの"Purple Cow"を置くべきだ、と主張しています。
この本は、マーケティングするべき価値のある商品やサービスを作りたいひとたち全員に向けた檄の書です。

22. Outliers: The Story of Success by Malcolm Gladwell (8.1% recommended)

22.jpg

Malcolm Gladwellによって書かれたこの驚くべき書は、我々を"異常者"の世界へと導く知識の旅に連れ出します。
"異常者"とはそう、世界最高の頭脳、最も有名な人、最も成功した人たちです。
彼は質問します。
いったい彼らは我々と何が違うのでしょう?

我々は、成功した人たちがいったいどのような者であるかということにばかり気を向け、彼らがいったいどこから来たのかということに気を払いません。
それはすなわち文化であり、家族であり、世代であり、そして彼らが育ってきた特異な環境です。
この旅の途中で彼は、ソフトウェア億万長者の秘密、伝説のサッカー選手になるために必要なこと、アジア人が数学が得意な理由、ビートルズが偉大なロックバンドになった理由を解き明かします。

21. The Power of Habit: Why We Do What We Do in Life and Business by Charles Duhigg (8.1% recommended)

21.jpg

数々の受賞歴を持つビジネスレポーターCharles Duhiggが、何故我々は習慣を持っているのか、そしてそれをどのように変えていくのかについて、スリリングな科学的発見を解説します。P&Gの役員室からNFLのサイドラインに、そして公民権運動の最前線まで、人間の性質やポテンシャルを理解するための膨大な量の情報が、我々を魅了する物語へと導きます。
そしてその核心で、この本は爽快な主張を行います。
定期的に運動すること、減量すること、より生産性を高めること、そして成功を収めること、これは全て習慣の働きを理解することです。
Duhiggが示したように、我々はこの新しい科学を利用することによって、ビジネスやコミュニティ、そして人生を変化させることができます。

20. Founders at Work: Stories of Startups' Early Days by Jessica Livingston ( 8.6% recommended)

20.jpg

起業家、イノベーター、あるいは世界最高のテクノロジーを産み出す科学と原動力に魅了されている人にとって、この本は魅力的な知恵と洞察を提供してくれます。
FaWは有名なテクノロジー企業の創業者へのインタビューを集めたもので、スタートアップの初期に何が起こったかについて描かれています。

19. Hooked: A Guide to Building Habit-Forming Products by Nir Eyal (8.6% recommended)

19.jpg

何百万という人々に手にとってもらいたい製品を作ろうとしているときに、この本はシンプルでありながらも非常に有用なモデルを紹介してくれます。
すぐに読めて(わずか140ページ)、要点を押さえた内容で、コンセプトとデザインの課題に新風をもたらしてくれました。
ユーザの行動をモデル化し、エンゲージメントを得るために必要だったのに見逃していた領域を把握するために、この本を利用しました。

この本のもうひとつの大きな価値は、(Twitter・Facebook・Pinterest・Instagramなどから)我々が毎日受けているとっかかりの詳細な分析です。
毎日をユーザ目線でよく観察することで、構造化された方法でコインの反対側を見通すことができるようになります。

18. The Innovator's Dilemma: When New Technologies Cause Great Firms to Fail by Clayton M. Christensen (9.1% recommended)

18.jpg

多くの企業が新しいイノベーションの波に乗り遅れてしまう理由を、Christensenが解説しています。
いかなる業界においても、経営者が伝統的な商習慣を捨てる方法とタイミングを知らなければ、既に確固たる地位を築いている製品で成功している企業もいずれ押しのけられてしまいます。

この本は、大手企業の成功と失敗の両方をガイドとして提供し、破壊的イノベーションを活用するための一連の手法を提供しています。

17. Art of the Start 2.0: The Time-Tested, Battle-Hardened Guide for Anyone Starting Anything by Guy Kawasaki (9.1% recommended)

17.jpg

Guy Kawasakiはビジネス界で最も独創的で不遜な戦略家の一人であり、その20年の経験から、何かを始める人のための必須のガイドを提供します。
1980年代のAppleにおいて、彼は一般消費者をエバンジェリストに進化させ、世界的企業に育てることに後見しました。
さらにベンチャーキャピタルであるGarage Technology Venturesの創業者CEOとして、何十社もの企業で彼のアイデアの実地テストを行ってきました。
そしてベストセラーのビジネス書や記事の著者として、スタートアップの夢を実現しようとしている何千もの人々にアドバイスをしてきました。

16. Tools of Titans: The Tactics, Routines, and Habits of Billionaires, Icons, and World-Class Performers by Tim Ferriss (9.1% recommended)

16.jpg

過去2年間、私はPodcast『The Tim Ferriss Show』で200名以上の世界クラスの人々にインタビューしてきました。
その対象はスーパーセレブ(Jamie Foxx、Arnold Schwarzeneggerなど)、アスリート(リフティング、体操、サーフィン選手など)をはじめ、伝説の特殊作戦部隊司令官や闇市場の生化学者まで多岐にわたっています。
ほとんどのゲストが、数時間のインタビューに応じてくれたのは初めてのことです。
この独自性により、『The Tim Ferriss Show』はビジネスジャンルとして初めて1億ダウンロードを超えたPodCastになりました。

15. Influence: Science and Practice by Robert B. Cialdini (9.6% recommended)

15.jpg

この書は、学術的な研究を含みながらも物語形式で書かれています。
Cialdiniは、営業マン・資金調達者・アドバイザーの仕事から得た、相手に如何にして"はい"と言わせるかのテクニックと戦略を、実験的な仕事から得た証拠と組み合わせました。
授業でも広く使われ、ビジネス界で活躍する人たちに愛読されているInfluence待望の改訂版は、説得力の強さを思い知らされます。

14. Traction by Gabriel Weinberg and Justin Mares (9.6% recommended)

14.jpg

この本は、あなたのビジネスに適した顧客の基盤を構築するための19の方法を教えてくれます。
Jimmy Wales (Wikipedia)、Alexis Ohanian (reddit)、Paul English (Kayak)、Dharmesh Shah (HubSpot)といった40人以上の成功したフォーリナーへのインタビューに基づいています。
たとえば以下のような学びを得ることができます。
・オフライン広告や、競合他社が使用していないチャネルを見つけて利用する。
・ターゲットを狭く絞った分野のメディア取材を受けることで、より多くの顧客を獲得する。
・自動更新をあえて不定期にすることで、メールマーケティングなどの効果を高める。
・オンラインツールやリサーチにより、検索エンジンの順位や広告効果を向上させる。

13. Rich Dad Poor Dad by Robert T. Kiyosaki (12.9% recommended)

13.jpg

本書は二人の父親、実の父親と、その親友である金持ちの父親、に育てられたRobertの物語です。
そして彼を形作った、二人の父親のお金と投資についての姿勢の話です。
あなたが金持ちになるためには高収入を得る必要があるという固定観念を壊し、あなたがお金のために働くこととお金があなたのために働くことの違いを解説します。

12. The 7 Habits of Highly Effective People by Stephen R. Covey (12.9% recommended)

12.jpg

有史以来書かれてきた中でも最も刺激的でインパクトのある書籍のひとつである本書は、25年にわたって読者を魅了してきました。
この本は社長、CEO、教育者に親たち、端的に言うと世界中のあらゆる年齢や職業、の数百万人の人生を変えてきました。
Stephen Coveyによる古典の25周年記念版は、彼の時代を超えた知恵を記念し、我々が大きな目的を持って生きていくことを勧めています。

11. Rework by Jason Fried and David Heinemeier Hansson (14% recommended)

11.jpg

ほとんどのビジネス書には、全く同じ古いアドバイスが書かれています。
ビジネスプランを書き、競合を研究し、投資家を探し、なんとかかんとか。
そんな本を探しているのであれば、かわりにこの本を手に取りましょう。

この本を読めば、何故計画が有害なのか、何故外部の投資家が不要なのか、そして何故競合を無視した方がいいのかを知ることができます。
あなたが必要だと考えているよりも、あなたが本当に必要としているものは少ないのです。
ワーカホリックになる必要はありません。
人員を増やす必要もありません。
ペーパーワークやミーティングで時間を浪費する必要もありません。
オフィスも必要ありません。
それらは全て余計なものです。

あなたが本当にする必要のあることは、口を閉じて手を動かし始めることです。
この本はその方法を教えてくれます。
あなたはより生産的になり、破産せず有名になり、直感的ではないアイデアに多くのインスパイアを得るでしょう。

わかりやすい言葉と容易なアプローチは、これまで自分で自分を律することを夢見てきた人たちのための完璧な脚本です。
ハードコアな起業家、中小企業のオーナー、嫌いなジョブを嫌々こなしている人、ダウンサイジングの犠牲者、飢えたくないアーティストは、この本から貴重な指標を得ることができるでしょう。

10. Start with Why by Simon Sinek (14.4% recommended)

10.jpg

Sinekは根源的な疑問から始めました。
何故、ある人や組織は、他の人よりも革新的で、影響力があり、収益性が高いのだろうか?
何故、一部の人々や組織は、顧客や従業員からの支持が高いのだろうか?
たとえ成功者といえど、何度も何度も成功を繰り返すことができる人は何故少ないのだろうか?

Martin Luther King Jr.とSteve Jobs、そしてWright Brothersにはほとんど共通点がありませんが、彼らはみなWHYから始まりました。
彼らは、製品やサービス、行動やアイデアの背後にあるWHYを理解しない限り、人々は実際にそれらを購入することがないことに気付いていました。

世界で最も影響力を及ぼしているリーダーたちは、全て同様に考え、行動し、コミュニケートしていたことをSTART WITH WHYは解き明かしています。
そしてそれは、大多数の人々がやっていることとは正反対です。
Sinekはこの発想をGolden Circleと名付けました。
それは組織を構築し、ブームを導き、人々を惹き付けるための枠組みを提供します。
そしてそれはいつも、WHYから始まります。

9. Think and Grow Rich by Napoleon Hill (14.4% recommended)

09.jpg

この本は、"すべての自己啓発本の祖"と呼ばれています。
何が勝者を作るのか、を大胆に問いかけた初めての本でした。
答えを追い求め、その答えを聞いた男、Napoleon Hillは今では世界の勝者の一人です。
成功の教師の中でも最も有名である彼は、彼の哲学である成功の法則を生み出すため、大金と半生を費やした内容をこの本に力強くまとめています。

1937年に出版されたこの本のオリジナルでは、Andrew Carnegie、Thomas Edison、Henry Fordといった当時の大富豪の物語から彼は成功の法則を導き出します。
更新版では、世界的に有名な著作家、講師、人事管理コンサルタントであり、Hillの思想を取り入れたエキスパートであるArthur R. Pellの手によって、Bill Gates、Mary Kay Ash、Dave Thomas、Sir John Templetonといった現代の億万長者がどのように富を手に入れたのかについての逸話が巧みに織り交ぜられました。
また時代遅れな例や難解な用語は、現代の読者にも理解できるよう書き改められています。

8. Good to Great: Why Some Companies Make the Leap and Others Don't by Jim Collins (14.9% recommended)

08.jpg

5年前にJim Collinsは、『"良い"会社が"偉大な"会社になれるのかだろうか。そうだとすればどのようにすればいいのか。』という疑問を持ちました。
本書においてJim Collinsは、それは可能であると結論づけましたが、しかし同時に銀の弾丸も存在しないことも発見しています。
Jim Collinsと彼の研究チームは1435の会社を調査し、時間経過で業績が大幅に改善された企業を探し、その理由の探求を始めました。
最終的に彼らはFannie Mae、Gillette、Walgreens、およびWells Fargoなどの11企業に狙いを定め、そして企業が成功するために必要とされていた従来の概念を覆す共通の特徴を発見しました。

"良い"企業から"偉大な"企業への飛躍には、耳目を集めるCEOや最新のテクノロジーは不要で、革新的なマネジメント、あるいは精緻なビジネス戦略すらも必要ありません。
真に偉大で希有な企業の柱には、ある一定の規則に従って考え、行動する人々を見つけ、育てるという企業文化がありました。
本書は、"偉大な"企業の例とそうではない企業の例を多数出しながら、あらゆる組織が規範とすべき十分に合理的なロードマップを提供しています。
"良い"から"偉大な"になるために、マネージャやCEOは目を通しておき、そして来たるべき時に再読すべき本です。

7. The E-Myth Revisited: Why Most Small Businesses Don't Work and What to Do About It by Michael E. Gerber (15.3% recommended)

07.jpg

驚異的なベストセラーの改訂版である本書は、ビジネスを始めることについての神話を払拭してくれます。
ビジネスコンサルタントである著者Michael E. Gerberは、長年の経験から得たその鋭い観察眼で、一般的な思い込みや期待だけではなく、ときには技術的な専門的知識さえもがビジネスを成功させる道を邪魔してしまう可能性があることを指摘しています。
Gerberは、生活をビジネスに応用する手段を提供します。
またフランチャイズでなかったとしても、フランチャイズの教訓をどのようにビジネスに適用するか、ということも示します。
最も重要なこととしてGerberは、見過ごされてしまいがちな仕事に"work in"することと仕事に"work on"することの違いを明らかにします。

6. The 4-Hour Workweek by Timothy Ferriss (15.8% recommended)

06.jpg

今一生懸命働いて定年後に休む、そんな昔ながらの古い概念は捨ててしまいましょう。
この先行き不透明な経済状況の中では特にです。
あなたの夢はラットレースからの脱出でしょうか、ハイクラスの世界旅行でしょうか、はたまた何もせずに月5桁ドルの収入を得ることでしょうか。
いずれにせよ、このThe 4-Hour Workweekはその将来への青写真となります。

5. How To Win Friends and Influence People by Dale Carnegie (22.5% recommended)

05.jpg

Dale Carnegieによる信頼性の高い確固とした助言は、数えきれないほどの人々の人生を、成功という梯子の上に運び上げてきました。
最も画期的で、時代を超えたベストセラーのひとつであるこの本は、人を動かすための方法を教えてくれます。
・人に好かれる六原則
・人を説得する十二原則
・人を変える九原則
他にも多くのことが書かれています。
1500万部を超える、21世紀の必読書です。

4. The $100 Startup: Reinvent the Way You Make a Living, Do What You Love, and Create a New Future by Chris Guillebeau (24% recommended)

04.jpg

著者のChrisはいまだ30代前半であるにもかかわらず、既に175か国以上を訪問しており、そして地球上のすべての国を回ろうとしています。
しかしながら彼は一度も就職したこともなければ普通に給料をもらったこともありません。
彼にはアイデアをお金に変える特別な才能があり、そして手に入れたお金で冒険の旅を送っているのです。

Chrisのような人は他にも大勢います。
伝統的な雇用体制から脱却し、自分にとって意味のあることを追求するための時間と収入を作り出す者たちです。
収入と情熱のあいだに収まるために、今やっていることを放り出す必要はありません。
少量の時間とお金をコミットして、ベンチャーとしてミニマムで始めることができます。
そして、成功すると確信してから本気を出せばいいのです。

3. The Hard Thing About Hard Things by Ben Horowitz (24.5% recommended)

03.jpg

Andreessen Horowitzの共同設立者であり、シリコンバレーで最も尊敬されている企業家であるBen Horowitzによる、スタートアップを設立し運営していくために必要なアドバイスです。
彼のブログを元にした、ビジネススクールでは決して教わらない、非常に困難な問題に立ち向かうための実用的な知恵です。

ビジネスを始めることがどれだけ素晴らしいことかを話す人は数多いですが、実際にそれがどれだけ難しいことであるかを正直に語る人はほとんどいません。
Ben Horowitzはリーダーが日々直面する問題を分析し、そしてテクノロジー企業の起業、管理、売却、買収、投資、監督などから得られた教訓を惜しみなく共有しています。
ラップが大好きな彼が、好きな曲の歌詞から教訓を学び、友人を買い越し、競争相手を買収し、CEOとしてのメンタリティを育て維持し、現金化の適切なタイミングを見極めることといった様々な話を率直に語っています。

Horowitzの個人的な、しばしば失敗した経験から導き出された本書には、彼のトレードマークであるユーモアとストレートなトークが満載で、ベテランの起業家だけではなく、これから新しく起業を目指す人にとっても貴重な価値を持っています。

2. Zero to One by Peter Thiel (29.3% recommended)

02.jpg

我々の時代に隠されている大きな秘密は、未知のフロンティアを開拓して新たな発明を作り出す余地はまだまだ存在するということです。
伝説の投資家であり企業家であるPeter Thielが、それら新しいものを見つけるための非凡な方法を本書で示しています。

本書はまず、我々はテクノロジーの停滞の時代に生きているのであるという逆説から始まります。
たとえキラキラのモバイルデバイスに囲まれていたとしてもです。
情報技術はたしかに急激に進歩していますが、しかしコンピュータやシリコンバレーだけが進歩しているわけではありません。
進歩は、どの業界でもどんな分野でも起こります。
そのためには、全てのリーダーがマスターしなければならない最も重要なスキルを持っていなければなりません。
すなわち、自分で考える方法を学ぶことです。

他の誰かが既にやっていることをやるということは、世界を1からnに変えるということであり、その技術をより身近なものにしてくれるでしょう。
しかし、まだ誰もしてないことをするということは、0を1にすることです。
次のBill GatesはOSを作らないだろうし、次のLarry PageやSergey Brinは検索エンジンを作らないことでしょう。
明日のチャンピオンは、今日の市場で競争して勝つことを狙ってはいません。
彼らのビジネスはユニークなので、競い合うこと自体が行われません。

本書はアメリカの未来の進歩を楽観し、新たなイノベーションの考え方を示してくれます。
それは、思いもよらないところに価値を見出すために疑問を投げかけることから始まります。

1. The Lean Startup by Eric Ries (44.7% recommended)

01.jpg

Eric Riesは、スタートアップを『極めて不確実性の高い状況下で、新しいものを創造することに専念する組織』であると定義しました。
これは、たった一人でガレージを仕事場にしている人から、Fortune500掲載企業の熟練プロ集団にも当てはまることです。
彼らに共通しているものは、不確実な霧の向こうを見通し、持続可能なビジネスとして成功させようという意志です。

資本効率と社員のクリエイティビティを、いずれも効率的に育てていくのがLean Startupのアプローチです。
製造業にヒントを得たこのアプローチは、検証による学びを重視しています。
迅速な調査、製品の開発サイクルを短縮させるための直感的ではない方法、恣意的な測定基準ではなく実際の進捗を調べることなどで、顧客が本当に求めているものを見つけ出します。
これにより、企業は計画を少しづつ、秒進分歩で俊敏に軌道修正していくことができるようになります。

本書は、精巧なビジネスプランを立てて時間を浪費するよりも、手遅れになる前にビジョンを継続的にテストして修正して適応させていく方法を提供します。
企業がこれまで以上にイノベーションを起こす必要のある時代に、成功するスタートアップを立ち上げ、管理するための科学的アプローチです。

Conclusion

いくつかの順位には驚いたかもしれませんが、これらの多くについては既に聞いたことがあるでしょう。

このリストを作るにあたり、いくつか気付いたことがありました。

・複数冊が入ったのはTim Ferrissだけでした。
・聖書が1回紹介されていました。
・最も多く出てきた伝記はWalter IsaacsonによるSteve Jobs biographyで、6%の人が勧めていました。

この記事を楽しんでいただけたら幸いです。

正直に言って、この記事を書くのにすごい時間がかかりました。
この気に入ってくれたらぜひリツイートしてください。
でも、このためだけにアカウントを作ったりしないでね。

dev.toのコメント欄

「素晴らしいリスト!」
「リーンスタートアップが1位←知ってた」
「4 hour work weekのおかげでスタートアップの世界に入ったよ。」
「どこの誰が紹介していたかの一覧がほしい。」
「リストを自動的に抽出するボットとか楽しそう。」

感想

訳しておいてなんだけど、私は自己啓発本に全く興味が無いので一冊も読むことはないです。

まあそんな奴は少数派なので置いておくとして、多くの人が紹介している本は良い本に違いないというマクドナルド理論に従うと、The Lean Startupが最も優れた書籍と言えるでしょう。
言えるでしょうというか実際めちゃくちゃ評判もいいし、それどころか本書一冊で方法論の一大ジャンルを築くまでになっていますからね。
それだけ世界に多くの影響を与えた、重要な書籍であることは間違いありません。
2位以下の本も、その多くは100万部を超えるようなベストセラーばかりです。
読めば自らのためになることは確定的に明らかです。

なお、どの書籍も多くのサイトで紹介される有名なものばかりだけあって、何れも日本語訳が出版されています。
興味があるなら探してみるのもいかがでしょう。
日本語にしてみると、あっこれ聞いたことある、って本もわりとあったりしますよ。

Browsing Latest Articles All 57 Live