将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察
The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...
無料のリソース、書籍、オンライン・クラス、コーディング・ブートキャンプが数多くある今、誰もがコーディングを学ぶことができる。しかし、コーディングとソフトウェア・エンジニアリングの間には、まだ質のギャップがある。あるべきなのだろうか?
私が初めて "Hello world "を書いたのは20年以上前のことだ。過去10年間、私はこのキャリアを楽しんできた。 コード プロのコーダーになって何年になるかと聞かれたら、そう答える。
私は何年選手なのだろう? ソフトウェアエンジニア?5年くらいかな。待ってください、この数字は一致しないようです!では何が変わったのか?誰をソフトウェア・エンジニアと見なし、誰を「単なる」コーダーと見なすか?
コーディングは比較的簡単だ。もう、とんでもなく制約の多いシステムでアセンブリ・ニーモニックばかりということはない。Rubyのように表現力豊かでパワフルなものを使えば、さらに簡単だ。
チケットを手に取り、コードを挿入する場所を見つけ、そこに置くべきロジックを考え、ブーム - 完了。もう少し上級者なら、コードをきれいにする。論理的にメソッドに分割されている。ハッピーパスだけをテストしない、きちんとした仕様がある。それが優れたコーダーの仕事だ。
ソフトウェア・エンジニアはもうメソッドやクラスで考えることはない。私の経験では、ソフトウェア・エンジニアはフローで考える。彼らはまず第一に、システム内を轟々と流れるデータとインタラクションの荒れ狂う川を見る。そして、この流れを迂回させたり変えたりするために何をすべきかを考える。きれいなコード、論理的な手法、優れた仕様などは、ほとんど後回しにされる。
人は一般的に、現実との相互作用のほとんどについて、ある種の考え方をする。言葉は悪いが、これを「トップダウン」の視点と呼ぼう。私の脳が取り組んでいるのがお茶を飲むことだとしたら、まず大まかな手順を把握する。キッチンに行く、やかんをかける、お茶を用意する、お湯を注ぐ、デスクに戻る。
私がデスクでボーッとしているときに、どのカップを最初に使うかを考えることはない。紅茶を切らしているかもしれない(あるいは少なくとも、紅茶を切らしているかもしれない)とは考えない。 良い もの)。大雑把で、反応が鈍く、ミスを犯しやすい。総じて言えば 人間的 自然の中で。
ソフトウェア・エンジニアが、少々気の遠くなるようなデータ・フローの変更を考えるとき、彼らは当然、同じような方法で変更を行うだろう。この例のユーザーストーリーを考えてみよう:
顧客がウィジェットを注文した。注文の価格設定において、以下を考慮しなければならない:
これはすべて作為的に見えるかもしれないが(そして明らかにそうである)、私が最近つぶす喜びを味わった実際のユーザー・ストーリーとそうかけ離れたものではない。
では、ソフトウェア・エンジニアがこの問題に取り組むための思考プロセスを見てみよう:
「ユーザーとオーダーを取得する必要があります。それから合計を計算します。ゼロから始めます。それからウィジェットの形状修正を適用します。それからお急ぎ料金を計算します。そして休日かどうかを確認します。
ああ、シンプルなユーザーストーリーがもたらす興奮。しかし、ソフトウェア・エンジニアは人間であり、完璧なマルチスレッド・マシンではない。エンジニアはさらに深く考え続ける:
"ウィジェットのシェイプ・モディファイアは......ああ、これはウィジェットに超依存していますね。しかも、ロケールごとに違うかもしれない。" 彼らは、以前はビジネス要件の変化で火傷を負ったと考えている、 "ラッシュチャージもそうかもしれない。祝日も地域によって違うし、タイムゾーンも関係してくる!Railsで異なるタイムゾーンの時間を扱う方法については、こちらの記事で紹介しました!スキーマをチェックしてみよう。"
わかったよ、ソフトウェア・エンジニア。止まれ。紅茶を淹れているはずなのに、食器棚の前でボーッとして、花柄のカップが紅茶の問題に当てはまるかどうかさえ考えている。
しかし、人間の脳にとって不自然なこと、つまり細部にわたって何段階もの深さで思考するようなことをしようとすれば、それは簡単に起こりうることなのだ。 同時に
タイムゾーンの処理に関するリンクの広々とした武器庫をざっと調べた後、エンジニアは気を取り直して、これを実際のコードに分解し始める。素朴なアプローチを試みた場合、次のようになるだろう:
def calculate_price(user, order)
order.price = 0
order.price = WidgetPrices.find_by(widget_type: order.widget.type).price
order.price = WidgetShapes.find_by(widget_shape: order.widget.shape).modifier
...
終了
そして、このように楽しく手続き的なやり方で、どんどん進めていくのだが、最初のコード・レビューで大きく打ち切られてしまうのだ。考えてみれば、このように考えるのはまったく普通のことなのだ:最初に大まかなことを考え、細かいことはずっと後で考える。最初に大まかなことを考え、細かいことはずっと後で考える。
しかし、私たちのエンジニアはよく訓練されており、サービス・オブジェクトを知らないわけではない:
クラス BaseOrderService
def self.call(user, order)
new(user, order).call
終了
def initialize(user, order)
ユーザー = ユーザー
オーダー = オーダー
終了
def call
puts "[WARN] #{self.class.name}のデフォルト以外のコールを実装する!"
ユーザー、オーダー
end
終了
class WidgetPriceService < BaseOrderService; end
class ShapePriceModifier < BaseOrderService; end
class RushPriceModifier < BaseOrderService; end
class HolidayDeliveryPriceModifier < BaseOrderService; end
class OrderPriceCalculator < ベースオーダーサービス
def call
user, order = WidgetPriceService.call(user, order)
user, order = ShapePriceModifier.call(user, order)
user, order = RushPriceModifier.call(user, order)
user, order = HolidayDeliveryPriceModifier.call(user, order)
ユーザー、注文
終了
終了
```
よし!これで、TDDを採用し、テストケースを書き、すべてのピースがうまく収まるまでクラスに肉付けすることができる。そして、それもまた美しくなるだろう。
理屈で考えるのはまったく不可能だ。
確かに、これらはすべて、単一の責任を持つよく分離されたオブジェクトである。しかし、ここで問題なのは、これらはまだオブジェクトだということだ。このオブジェクトを無理矢理関数のように見せかける」というサービス・オブジェクト・パターンは、実のところ松葉杖なのだ。誰かが HolidayDeliveryPriceModifier.new(user, order).something_else_entirely
.これらのオブジェクトに内部状態を追加することを妨げるものは何もない。
言うまでもない。 ユーザー
そして オーダー
もオブジェクトであり、それをいじるのは、誰かがこっそり素早くやるのと同じくらい簡単だ。 オーダーセーブ
このような "純粋な "機能オブジェクトのどこかで、真実の根底にあるソース、つまりデータベースの状態を変更してしまうのだ。しかし、このシステムが複雑化し、さらに多くの非同期的な部分へと拡張された場合、これは確かにあなたに跳ね返ってくる可能性がある。
エンジニアは正しい考えを持っていた。そして、このアイデアを表現するごく自然な方法を使った。しかし、このアイデアを美しく、推論しやすい方法で表現する方法を知ることは、根底にあるOOPパラダイムによってほとんど阻止されていた。そして、自分の考えをデータフローの転用として表現することにまだ飛躍していない人が、基礎となるコードをあまり巧みに変更しようとすれば、悪いことが起こるだろう。
もし、自分の考えをデータフローで表現することが簡単なだけでなく、必要なパラダイムがあったとしたら。不必要な副作用をもたらす可能性がなく、推論をシンプルにすることができたら。お茶を入れる花柄のカップのように、データが不変であれば。
はい、もちろん冗談です。そのパラダイムは存在し、関数型プログラミングと呼ばれている。
上記の例が、個人的に好きなElixirでどのように見えるかを考えてみよう。
defmodule WidgetPrices do
def priceorder([user, order]) do
[ユーザー, 注文]
|> ウィジェット価格
|> 形状価格修飾子
|> rushpricemodifier
|> 休日価格修飾子
終了
defp widgetprice([user, order]) do
%{widget: widget} = オーダー
price = WidgetRepo.getbase_price(widget)
[ユーザー, %{ 注文 | 価格: 価格 }].
終了
defp shapepricemodifier([user, order]) do
%{widget: ウィジェット, price: 現在の価格} = 注文
modifier = WidgetRepo.getshapeprice(widget)
[user, %{order | price: currentprice * modifier} ]を実行する。
終了
defp rushpricemodifier([user, order]) do
%{rush: rush, price: currentprice} = オーダー
if rush do
[user, %{order | price: currentprice * 1.75} ]。
else
[user, %{order | price: current_price} ] を実行する。
終了
終了
defp holidaypricemodifier([user, order]) do
%{日付: date, 価格: currentprice} = order
modifier = HolidayRepo.getholidaymodifier(user, date)
[user, %{order | price: currentprice * modifier}]を実行する。
終了
終了
```
これは、ユーザー・ストーリーが実際にどのように実現されるかを完全に具体化した例であることにお気づきかもしれない。これは、Rubyの場合よりも説明が少ないからです。私たちは、Elixir特有のいくつかの重要な機能(しかし、関数型言語では一般的に利用可能)を使っています:
純粋な機能。 私たちは、実際に受信を変更することはない オーダー
私たちはただ新しいコピーを作るだけ、つまり初期状態の新しい反復を行っているだけなのだ。何かを変更するためにサイドに飛ぶこともない。仮にそう望んだとしてもだ、 オーダー
を呼び出すことはできない。 オーダーセーブ
というのも、それが何なのかがわからないからだ。
パターンマッチング。 ES6のデストラクチャリングに似ている。 価格
そして ウィジェット
仲間に無理強いするのではなく、オーダーから外し、それを引き継ぐのだ。 ウィジェットレポ
そして ホリデーレポ
満員電車にどう対処すべきか オーダー
.
パイプオペレーター。 で見た 価格オーダー
を走らせたことのある人ならすぐに馴染みのある概念だ。 ps aux | grep postgres
まだ動いているかどうかを確認するためだ。
副作用は、私たちの思考プロセスの基本的な部分ではない。カップにお湯を注いだ後、ケトル内のエラーでオーバーヒートして爆発するのではないかと心配することは通常ない。 注水後の爆発
高めに反転した。
コーダーからソフトウェア・エンジニアになるには、オブジェクトやステートを気にするだけでなく、データ・フローを気にしなければならない。OOPで育ったあなたにとっては、確かにそうだった。関数型言語では、フローについて考えるようになる。 最初の夜に.
我々は ソフトウェア工学 私たち自身にとっても、この分野に新しく入ってくるすべての人にとっても、複雑なことなのだ。プログラミングは難しく、頭を使う必要はない。簡単で自然なものでいいのだ。
ややこしいことはやめて、もう機能的に行こうよ。それが私たちの考え方なのだから。
あわせて読みたい: