Ruby FiberとかRactorを理解する上で理解しておく良さそうなこと
Ruby Kaigi 2020 TakeoutのMatzさんのキーノートで話されていた「Ractor」や「Fiber」、その話の中で出ていた「非同期I/O」について色々分かってないことが多かったので、前提知識として必要そうなことを纏めてみた
Matzさんが話していた内容
- プログラミング言語の方向性を決めることは難しいということ。互換性を保ちながら良いものにする!
- Ruby 3で目指す内容のコンセプトは、Faster(高速化) / More Productive(高い生産性)で、そのために ①Fast、②Concurrent、③Correct。つまり、早くて並列処理可能で正しくプログラムを書ける状況を提供していく
FiberやRactorは、Concurrentの話
並列化に関する話の中で、話される内容である。 そもそも、ソフトウェアを並列化する仕組みとして、
- マルチプロセス(メモリを共有しないが、コンテキストスイッチの大きい方法)
- マルチスレッド(メモリを共有し、コンテキストスイッチを抑える方法)
などの考え方で行われる。RubyはGILと呼ばれる グローバルインタプリタロック という考えの仕組みによって、並列で処理が行われることは無いのである。
蛇足だが、 ではなぜ、RubyがGILによって排他ロックを行っているかというと、おそらく...CRubyで使用されるライブラリがスレッドセーフではないためすぐ死んでしまうのだという理解である
本題に戻ると、Ruby 3のコンセプトとそのアクションである Concurrent は、この箇所に対する、いろいろな対処法を提供していくものである。
アイディア1 「非同期の I/O Fiber」
「実行が終わったら、ココに結果が格納されるよ」という、非同期によるI/OはJavascriptの世界ではPromiseやasync/awaitなどによって既に提供されていている。単にI/Oの多重化だけでは、マルチコアを活用しているとは言い切れないが、I/Oの比重が高いアプリの場合は、性能が出やすくなる。
Ruby 3ではそれを、 Fiber を使って非同期I/Oを提供するというアイディアである。ちなみにRuby 2では、ブロッキングI/Oであるとのことだが...。さていよいよ、わからなくなってた。ブロッキングI/O、非同期I/O?何が違うのだろう。
ブロッキングI/Oと同期I/O
ブロッキングI/Oとは、複数の処理のあるプログラムにおいて、I/Oの処理を待ってから進む
例えばread I/Oを行うシステムコールを行なったあと、そのシステムコールが処理を終えるまでユーザモードに戻る(コンテキストスイッチされる)という挙動で、コレを同期的、すわなち順番に1つずつやっていくものとなる
ノンブロッキングI/O
ノンブロッキングI/Oとは、例えば read I/O が行われたら、エラーが帰ってきて「マダ準備出来てないから後で来てな」と言わんばかりに、呼び出し元に再度問い合わせしてもらうことを想定しているのである
非同期I/O
ノンブロッキングI/Oと違うのは、再度、聞きに行くという処理を呼び出し元にさせないで、 準備できたから通知するね という形で、呼び出し元にシグナルやらコールバックやらで通知してあげてくれるのである。
非同期I/Oはファミレスの予約
非同期I/Oをあえて例えるなら、 近くのファミレスの予約である。ファミレスに到着したら、満員だったので予約システム(キオスク端末)に予約して、LINE連携したらLINEで呼び出し(通知)されるというもので、呼び出し元からすると多少は楽である。
ノンブロッキングI/Oは役所に提出した書類
ノンブロッキングI/Oをあえて例えるなら、 頑張って書類を整えて、やっとのことで役所に書類を提出したあとに、「まだですか?」という形で何度も窓口に聞きに行って「すみませんマダ終わってません」という、呼び出し元からすると少々手間のかかるやり方となる。 (最近だと、番号札を渡されて、呼び出してくれるので非同期I/Oかもしれない。)
しかし、両者の方法はどちらにしても、 エラー後から再度聞きに行く間の時間や、通知を待っている間 は別の処理を進められる出来るわけで、同期ブロッキングI/Oと比べると、他の処理を進められるぶん、ありがたい話なのである。
アイディア2 「Ractor」
マルチコアを活用するアイディアとしてのRactorである。CPUがボトルネックになるタイプの問題を解消していく。
複数のthreadでコードを動かすと、どのような問題が発生するのか。それは、状態(state)をスレッド同士が参照、共有し合う事によって、挙動がバグってしまうような事柄であり、そのためRactorは状態を共有しない方針となっている。
さて、実はこのような事柄。すなわち、スレッドセーフではない状況などにおいて、予期していない不整合が起きてしまうことには名前がついており、それを rase condition という。
race conditionってなんだろうか
各スレッドはバラバラに動く(非同期に動く)。その結果、処理した順番等によって結果が変化してしまう状態のことを race condition という。複数スレッドで共有された変数を叩く場合など、 data race という言葉もある。これは、同じ箱を同時に処理してしまって、どういう結果になっているのか全く想像ができないことを指す。
さて、本題に戻るとする。ではRactorは何も共有できないかというと、そうではない。以下の3つは共有できるのである。
- Immutable Object
- Deeply Frozen Objects (変数が指すオブジェクトに対してもFrozenさせる)
- Class / Module
など、要は 値が書き換え不可能になっているもののみ 共有することが出来る。ClassやModuleはオープンクラスであるRubyにとって、Immutableではないため一見「状態が変わりえるやん...」と思うかもしれない。しかし、Lock(コレはわからん)という仕組みによって、ClassやModuleをFrozenすれば共有しあえるようになるようである。
ソフトウェアの品質とはなにか
ソフトウェアの品質
オブジェクト指向入門 第2版 原則・コンセプトの第1章のまとめです。
工学の目的は品質である。したがって、ソフトウェア工学は、品質の高いソフトウェアの生産を意味する。
ソフトウェアにおける品質には2種類ある。
- 内的品質要因
- コンピュータの専門家にしか認識されない品質要因
- 外的品質要因
- ソフトウェアを利用するユーザが認識できる品質要因
最終的な問題は 外的 な品質要因である。いくつかある外的品質を達成しやすくするために、内的な品質要因と関係はあるが、本質を見失ってはならない。
外的品質要因
以下に上げる要因は特に重要な要因であり、オブジェクト指向ソフトウェアが目指すべき使命*1である。
正確さ
正確さとは仕様によって定義されているとおりに仕事を実行するソフトウェア製品の能力である。
この要因が守られていない製品はそれ以外の要因を無意味とする。ナニワトモアレ、仕様通りの動きを行うべきである。 また、正確さは 前提条件依存 である。下の層が正しいという前提で実装する方法であり、問題の関心を分離し、問題に集中する唯一の現実的な方法である。
頑丈さ
頑丈さとは異常な条件に対して適切に対応するソフトウェアシステムの能力である。
正しさの補完的な要因。 仕様以外 のところで起きることに対する能力である。予見できる異常な状態は、異常な状態とは言えず、 正確さ の範疇であることを留意する。
拡張性
拡張性とは仕様の変更に対するソフトウェア製品の適応のしやすさである。
ソフトウェアは変化する。 変わりやすさがある 。伝統的なソフトウェア工学では、変化に対して十分な考慮がないが、現代のソフトウェア工学では、変化を許容する。 拡張性を向上するためには、2つの原則がある。
- 設計の単純さ: シンプルな方が変更に適応をしやすい
- 非集中化:モジュールの自治性(基準/規則/原則に応じている)場合、変更後の全体への連鎖反応を減らせる
互換性
互換性とは、ソフトウェアの要素、ほかのソフトウェア要素との組み合わせやすさである。
ソフトウェアは他のソフトウェアと相互に作用し合う。ソフトウェアがそれ単体で存在するものではない。よって、互換性が重要である。 設計の同質化と、プロトコルが一致することを目指す。
効率性
効率性とは、処理時間、内部記憶および外部記憶上の空間、通信装置で使用する帯域幅などのハードウェア資源をできる限り必要としないソフトウェアシステムの能力である。
効率性は以下の理由によって無視をできない。
- 来年、50%機能が向上するハードウェアが存在しても、向上分は他の機能にあてがわれるため。ハードウェアの性能向上に依存することは運任せてきなところがある。
- 良いアルゴリズムであれば、ハードウェアの性能向上の恩恵を受けやすい
- 効率性が 正確さ に影響を与えるケースがあるため。天気予報のシミュレーションに24時間かかってしまったら、正確さがないがしろにされていることになる
効率化に対するソフトウェア業界は2つの態度に分かれる。固着する人と軽視する人。バランスを取らねばならない。
可搬性
可搬性とは多様なハードウェアおよびソフトウェア環境へのソフトウェア製品の移植のしやすさである。
使いやすさ
使いやすさとは、経験も資格も異なる人々がいかに容易にソフトウェア製品の利用法を学習し、問題解決に応用できるかである。これにはインストールや、操作、監視の容易さも含まれる。
成功したソフトウェアは、最初に意図したユーザより広い範囲のユーザに使用される様になる。よって、ユーザについての前提条件はできるだけ作らないほうがよい。 ユーザは人類で、読むこと、マウスを動かすこと、ボタンをクリックすること、ゆっくりとキーボード入力することのみを前提条件とする
古典的で有名な例を2つあげると、 FortranはIBM 704のプログラミングをするエンジニアと科学者の小さなグループの問題を解決するツールとして考えられていた。Unixはベル研究所内部で使用する意図で作られた
機能性
機能性とは、そのシステムが提供できるサービスの範囲である。 プロジェクトリーダーが直面する最も困難な課題の1つは、どの程度の機能性があれば十分かを判断することである。この業界では 機能主義 という呼び方で知られているが、より多くの機能を求める圧力は常に存在する。
機能主義の問題は2つの分けることができる。簡単な問題とより困難な問題である。 簡単な方は、「新しい機能の追加によって使いやすさが損なわれ、一貫性が失われるということ」である。 新しい機能の追加によって複雑になったというユーザの声を鵜呑みにしてはならない。私にとって不要だが、他者にとっては不可欠な機能である可能性がある。
難しい方は、「機能に集中するあまり他の品質要因を忘れがちになること」である。 より多くの機能を追加する場合、 熱狂的な競争の中で全体的な品質は見失われてしまう 。
適時性
適時性とはユーザが必要としているとき、または、必要とする前にソフトウェアシステムをリリースできることである。
偉大なソフトウェアでも、機を逃せばターゲットそのものを失う恐れがある。適時性は常に圧力がある。
番外: 保守性
保守は、ソフトウェア製品が納品された後に発生するものであるため、ソフトウェアの方法論を論じる場合はあまり語られない。ただし、 ソフトウェアにかかるコストの70%は保守に費やされているため、この側面を無視したソフトウェアの品質研究は完全とは言えない
保守コストの内訳は1位はユーザ要求の変更である。つまり、 ソフトウェアは変化する 。仕様のみならず、納品され一時期でも使用されていたソフトウェアすら変化する。2位は、データフォーマットの変更である。
なぜデータフォーマットの変更にコストがかかるのか
プログラムのある部分がデータ構造を知っている...ということが問題ではない。そもそもプログラムとは、内部処理をする過程で データにアクセスしなければならない 。内部でアクセスされるデータ構造が変更される場合、データのアクセスが行われる各所は、 そのデータがどんなデータであるか を 前提条件として 、プログラム全体に埋め込まれているためである。
これは、仕様変更の概念的大きさと釣り合わない大規模なプログラム変更の必要性を示唆する。
また、研究により以下のことがわかっている
- 効率性向上のための修正は割合が少ない。ひとたびシステムが動作すると、システムを破壊するよりはそっとしておくことを選ぶことが多い。
- 新しい環境への移植の割合も少ない。可搬性については、2種類しかなく、中間は少ない。つまり、 もともと可搬性を考えられて作られたプログラム とそうでないプログラムである。後者はあまりにもコストがかかるので、誰もやらない。
特に重要な事柄
- 正確さと頑丈さ: 上記でも述べられている通り、他の要因をナシにするほど大事な事柄である。実現のために、体系的なソフトウェア構築、形式的な仕様、CI/CD、そもそもの欠陥に気づける各種仕組み(型、例外処理など)などがある。この2つの要因をあわせて 信頼性 とも言う
- 拡張性と再利用性: ソフトウェアをモット変えやすく。この2つの要因をあわせて モジュール性 とも言う
「あなたのなりたい姿はなんですか?」に答えられますか。
この記事は、Speee Advent Calendar 2018の12月12日の記事です。
「あなたのなりたい姿はなんですか?」
と聞かれることありませんか?
私が務めている会社では、ある期間に1度、個人の目標設定を行う機会があります。 そこで、1on1などを通じて「あなたのなりたい姿はなんですか?」ということを聞かれます。 問いに答え、私なりの なりたい姿 を定義して、会社の目標の設定をしていくわけです。
過去を振り返ると、今まで色々な なりたい姿 を定義してきました。(今見ると、色々突っ込みたくなる)
- デザイナー的視点を持てるエンジニアになる
- 業務の効率化を行ない、自身の技術スキル向上を行う
- すごいOSSプロジェクトを作って、エンジニアとしてのプレゼンスを高める
...などですね。
さて、ここ数年で私のなりたい姿は一体何度変化したことでしょうか。もはや数えることが困難です。それぐらいたくさん変化しました。 「なぜか私はなりたい姿がブレる」ということを、常々感じでいました。それは目標設定の度に感じていました。なので、「あなたのなりたい姿はなんですか?」という問いに対して、どうしても「どうせまたズレるしな...」という、ネガティブな感情がありました。「今回はどんな なりたい姿 を定義すれば良いのか...。」
この問題のブレークスルーは一瞬で訪れました。 目標設定が始まり、色々な方と一緒に目標設定を揉んでいただいている中で、思わず、
「ぶっちゃけたところ、今までOKRとか、個人目標とかを立てて来て、一回もしっくり来たことがない。もはや何のためにやるか分からず、困っている。どのようにすれば私は目標設定ができるようになりますか?」と。
すると
「目標設定はね。binoさん、あなた自身のためにある。あなたの将来のなりたい姿と、今やっている仕事が連動していなければ、つらいでしょう?」(一字一句は違う)と。
今までも同じく「目標設定は自分自身のためにある」ことを認識していたつもりです。しかし、この場でお答え頂いたことは、特に素直に受け入れることができました。 チームや会社に対する 心理的安全 が、今までよりも高い状態となっているため、素直に受け入れることができたかもしれません。こんな質問を素直に行えることは本当にありがたいことだなぁと思います。
私がこの話を素直に受け入れることで、目標設定に対して「 もっと自由に設定しても良いんだ 」ということ心の変化が起きました。
もっと自由に設定してもいいとは、 会社やチームのために自分がどう動けるか という思考の流れから目標を設定するのではなく、 自分が本当になりたい姿からブレイクダウンして、会社で働くメンバとして、チームのメンバとしての目標を設定することができることに気づいた ということです。
つまり、今まで必死に定義してきた「 なりたい姿 」とは、確かにその時点ではなりたい姿ではあるが、 組織にも個人にも利益を享受できる状態でなければならない という無意識な思考が働いていることで、内発的なモチベーションによって自分自身の行動を目標に向けて変化させることが難しい状態だったのだと思います。
すみません。分かりづらいですよね。
つまり、
個人の目標は「みんながハッピーになる目標を設定しておくことで、自分も個力の増強につながるし、みんなも嬉しいじゃん」
ではなく、
「俺はこんな生き方をしたいんだ。この組織、このチームではこの生き方に近づくために何を行動できるんだろう」
という思考の流れで個人の目標を設定すればいいのです。 また別の言い方をすると、 「 個人の目標の設定とは、誰にも影響を受けない最も自由度高く行うことができる目標の設定である。 」ということです。
すると、どういう事が起きるでしょうか。
この考え方を知った上で行う目標設定は、 もはやネガティブな印象は存在しません 。なぜなら、 自分のなりたい姿を見つけられて、しかもそれに前進するための計画を立てるため です。こんなに楽しいことは無いですね。なので、私は気持ちが楽になりました。「 個人目標って自分を成長せるためのツール 」なのです。
次に、「私が本当になりたい姿とはなんだろう」と考え始めます。これが難しいです。私はどんな人間になりたいのか。3年後、5年後、10年後の姿を考えてみてくださいってこんなに考えるのが楽しいことなのかと。
ちなみに私は、この発見は 非常に可搬性のある思想を発見した と思っています。 齢を重ね、いくつになっても使える思想です。なので、この事実にもっと早く気づいておけばよかったと悔やむことはありません。「このコトを発見できて、人生儲けもん」だなぐらいです。
本題に戻ります。「私が本当になりたい姿とはなんだろう」と考えてもすぐに出てきません。しかし、誰でも楽しく考えられるはずです。そこにノウハウなどは無い気がしていて、「 ただあなたの好きなことってなんですか? 」と聞かれているだけです。(今までも同じことを聞かれていたが、考え方が変わったので今までとは別の思考で考えを巡らせることができます)
とはいえ、出ないです。もし難しいと感じられている方は、なにか相談できる人に相談するのが良いです。私は道を歩いてるとき、友達と会話してるとき、仕事に没頭しているときも、ふと「これは私のやりたいことかもしれない」と思いつくことがあるので、付箋にメモったりメモ帳にメモったりしてます。
ちなみに私のなりたい姿は「 三度の飯より楽しいことをやり続ける 」 ことです。具体的なものはまだ見つかってないです。「フロントエンドの○○を極める」とかはまだないです。でも、楽しいことをやり続けるようにしたい。おそらく、言葉にできていないだけな気がしています。もうあるけど、言葉にできていないだけということです。
「なりたい姿」が定義できている事自体が、組織にどんな影響を与えるでしょうか。それは、チームの足並みを揃えられることだと思っています。
チームのOKR。Objectiveとは本来「 チームメンバがみんなワクワクすること 」を設定するべきです。チームメンバの各々が、 個人目標というツールを使って「なりたい姿」を定義していれば 、 チームメンバ全員がワクワクするOKRを設定しやすくなります。実例として、あるプロジェクトでチームOKRを設定するとき、「なりたい姿」をチームメンバ全員が披露し、そこからプロジェクトの方向性とOKRのObjectiveを設定することがありました。個人の利害とチームの利害の一致が、すぐに行うことができたわけです。なので、大変効率よく、そして質の高いOを設定できたと思っています。
「なりたい姿」を定義する個人目標とどうやって組織と協調していくか。それをまとめてみた図です。如何でしょうか。イメージは湧いていただけましたでしょうか。
この記事が、どなたかの悩みの解消に一役買うことが出来れば大変嬉しいです。明日の12月13日は、 @kawakubox さんです。よろしくお願いします!
Slackでヘタクソな日本語/英語の校正をしてくれる「Botlint」を作ってる
この記事はSpeee Advent Calendar 2017 17日目の記事です。
前日は@yoppiによる「Amazon ECS上のタスクをGolangで操作する」でした。
次回は@mncによる「Webアプリケーションのパフォーマンス改善について」です。お楽しみに!
年の瀬12月、皆様はいかがお過ごしでしょうか。
突然ですが
私は告白します。
私は日本語も英語も苦手です。日常会話や仕事のやり取りで困らない程度にはコミュニケーションを取れると思っているんですが、得意な方ではないです。 自然言語が得意でないとどのようなことでツマヅクと思いますか?私は、仕事でWebエンジニアとしていろいろとコードを書く機会がありますが、大事なことはなんでしょうか。
頭の中、伝えづらいことをいかにして、文字・言葉・図として表現して、他人(それは1ヶ月後の自分でもある)に伝え残すかです。
問題解決を破綻した考え方によって進められていないか。その考え方は効率よく他の人が理解できる形になっているのか。 それは自分ひとりだけでは確認できないので、同僚だったり上長にその正しさを理解してもらう必要があると思うのです。(例えば、コードレビューをしてもらうなど)
防げるミスは防ぐ。気づかせてくれる。
人に伝える上で、相手に理解されづらいコミュニケーションってなんでしょう。
- 相手と自分の理解している度合いの違い。例えば、オートマのクルマに乗っている人に、渋滞のときのクラッチをつなぐ行為が大変である、と伝えるのは難しいです。
- 言葉の使い方。例えば、「いくらが零れ落ちそうなほど大量のおにぎりを食べてる。」という言葉を伝えたとき、大量のおにぎりの話をしているのか。大量のいくらが乗ったおにぎりの話をしているのか。しかし、大量のおにぎりを食べても、いくらが零れ落ちることはないから、それはありえないな...と。理解するのに時間がかかってしまう。
- 誤字脱字や常用漢字外の言葉で表現しているとき。「遙か彼方に小形飛行機が見える」とは、分かるけど使い方が間違っている。
私は、言語を扱う専門家ではないので、経験則による感覚の話になりがちですが、「誤字脱字や常用漢字外の言葉で表現しているとき。」というのは、まあそうだろうなと合点いただける人が多いのではと思うのです。
Slackで日本語/英語を校正してくれる「Botlint」を作った
というわけでこの度、その問題を解決すべく「Botlint」という、Slack上で発生したコミュニケーションの誤りを校正してくれるBOTを作ってみました。 どういうものかというと、
- このBOTがジョインしているチャネルで発言された内容をすぐに校正して、秘密裏に教えてくれるBOT。そして、コソッと edit するなど。
- Yahoo!の日本語校正サービスや、Textlintを用いる
- Textgearsで英語校正もしてくれる。Google Translateで、正しい英語を使っているかの裏付けをする。
というものです。実際にどのように伝えてくれるかというと...
こんな感じです。
Botlintから返される校正は、その発言を行った人しか返されないです。 したがって、色々な人がいるチャネルでも、このBOTを仕込ませておいて、文章のコーチングをしてくれる環境を実現できます。
どうなっているのか
Botlintは、Botkitをベースに、文章検出時にリアルタイムに下記とのやり取りを行っています。
普段仕事で、Reactを使うことが多いので、ES6とFlowtype、ESlintなどを導入。テストはまだないので、avaで書こうかなと思っているところです。コードもそれほど記述量がないので、技術的なことは直接コードを読んでいただくほうが早いと思います。
試し方
ここからは、実際にBotlintを試す方法についてです。今回は、Google App Engine で動作するように設計してあります。必要なことはそれほど多くありません。
./app.yaml.sample
を基に、Slackのトークンと、Textgears / Yahoo / Google のトークンを設置します。ちなみに、すべてのtokenを持ってくるのがめんどくさい方は、Slackのtokenさえ設定いただければ、Textlintによる校正だけを行ってくれます。tokenを取得する細かな方法は割愛しますが、GOOGLE_TOKEN
を取得した際の、プロジェクトには、Google Translate API
のAPIを有効化しておいてください。また、API Keyとして取得をして、記述してください。- Google Cloud Platform上に、適当なプロジェクトを作り、gcloud コマンドで操作できるようにしてください。そして、
google app deploy
を実施してください。 - Slackのtokenを取得する際に、BOTの名前を設定すると思います。そのBOTを任意のチャネルにジョインさせ、適当な日本語を書いてみてください。先程のスクリーンショットのように、校正してくれると思います。
採用経緯
今回、4つのサービス、アプリケーションで校正を支援してくれていますが、それぞれを採用した意図は下記の通りです。
- Textlintは、最小限の準備でbotlintを扱えるように。
- Yahoo!の校正支援APIは、Textlintでは校正できない、高度なサジェストを期待して導入。
- Textgearsは、そうですね。英語の校正をしてほしいと思ったとき、APIでその結果を受け取れるサービスがここ以外見つけられなかった...という経緯もあります。 本当は、grammarlyとか使いたい...。
- Google Translateは、僕だけかもしれないんですけど、英語で文章を書いたあと、それが正しい意味になっているかを確認するために、よくGoogle翻訳に掛けているのですが、それを自動でやってくれるようにした。
という具合です。
実際に使ってみたときの様子
英語の校正と翻訳をしてくれているときの様子。
実験しているときの様子1
実験しているときの様子2
自分の分報チャネルにbotlintを導入したら、皆様が使っていただきました。ありがとうございます。
所感
日本語の校正支援に関しては、日本語を操る日本人にとってはもう少し、高度な校正を期待しつつ、もしかしたら日本語を習う外国の人であれば有意義かもしれないなぁと思ってたりします。 英語に関しては、Google Translateと、TextGearによる掛け合わせは結構いい感じで、Gitのコミットコメントなどを書くときに重宝しますし、先日海外の方とAirbnbでやり取りをしてたのですが、その際もどんな文章を掛けばいいのかそのヒントになったりしました。 普通のGoogle TranslateやGrammaryなどの校正サービスを単体で利用するときとくらべて優位なのは、
- Slackのパブリックチャネルなど見えるところで文章を書けば、人間校正もしてくれたりしないか、、、そんな甘い期待ができる
- どんな文章を書いたか、ログが残る。英語ができるようになった実感も持てるのでは?
- 「この英語正しいんだっけ?」という不安を、英語によるコミュニケーションを行うときに低減できる。すなわち、話の内容に集中できるかも
という感じですかね。 ぜひbotlintを導入して、良さそうだなと思ったら、Github star をつけていただけると嬉しいです。
長文観ていただいてありがとうございました。
OS X Mavericksで、デュアルディスプレイが便利になった
MacでデュアルディスプレイするときのイライラがMavericksにすると解決します!
続きを読む株式会社Abejaさまのインターンにイッターン
インターン終了から時間が経ってしまいましたが、残します。
続きを読む