Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【植田】料金シミュレーションAPIの実装 #43

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

Ray-Gee
Copy link

@Ray-Gee Ray-Gee commented Dec 9, 2023

概要

serverside_challenge_1 を実装しました、レビューお願いします。

やったこと

ユーザーから契約アンペア数(A)と1ヶ月の使用量(kWh)を受け取って、プランごとの電気料金を返すAPIを実装

確認方法

cd serverside_challenge_1/challenges/ryuichi_ueda
rails s

エンドポイント

ttp://127.0.0.1:3000/api/v1/plans/all/${ampere}/${usage}の形式で実装

http://127.0.0.1:3000/api/v1/plans/all/40/300

動作確認

  • 30アンペア以上
    スクリーンショット 2023-12-09 22 13 17

  • 30アンペア未満
    スクリーンショット 2023-12-09 22 12 52

  • 無効な使用量(0未満)
    スクリーンショット 2023-12-09 22 12 26

  • 無効なアンペア(10, 15, 20, 30, 40, 50, 60以外)
    スクリーンショット 2023-12-09 22 12 01

  • 無効なエンドポイント
    スクリーンショット 2023-12-09 22 11 26

@Ray-Gee Ray-Gee marked this pull request as ready for review December 9, 2023 13:23
end

def total_charge(ampere, usage)
(basic_charge(ampere) + usage_charge(usage)).round
Copy link

@keishi1129 keishi1129 Dec 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「電気代 端数」で検索すると、「電気料金の合計額は円未満切り捨て」というふうに出てきます。

業界人でなければ普通知らないと思うのですが、業界だと当たり前なので顧客から明示的に仕様が提示されることは稀です。ですのでこの辺はENECHANGEのエンジニアとしては、しっかりと把握しておかなければなりません。

その上で、端数も出すという仕様にすることもあり得るかもしれませんし、プロジェクトには何年もこの業界で働いている人もいれば、入社したばかりの方もいますし、顧客においても不慣れな方がいるかもしれませんので、やはり認識合わせをしておくのが無難ですね。
植田さんも、「明示的ではないな、わからないな」と思った際には自身のコメントとともにレビュー依頼をかけていただくのが無難かなと思います。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、これは調べれば分かる事だったので自分の判断で四捨五入したのはミスですね、すいません、修正します。

it '正しい合計料金が計算される' do
expect(tokyo_gas_plan.total_charge(30, 300)).to eq(expected_charge_30_300)
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

アンペア20を入れた時の異常系のテストも欲しいと思ったのですがいかがでしょうか。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですよね、このファイルより手前で30未満が渡らないようにしていたので、そのバリデーションがない場合はここでエラーになってしまっていたのでどう書けば良いか分かりませんでした。
今回は設計ごと変更してこのファイルがなくなるので次回からテストが書けない場合は設計を疑おうと思います、ありがとうございます。

def initialize
super('JXTGでんき')
end
end
Copy link

@keishi1129 keishi1129 Dec 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

弊社では何千というプランを管理しています。

この設計にしてしまうと、今後プランが増えた時に毎回プランモデルを作らないといけない気がしますがいかがでしょうか。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

チャレンジの説明の中にも下記のポイントが記載されていますので、すでに工夫されていたら、もっと楽になるようにお願いします!

「同様の料金体系のプランが対象プランに追加された場合も、ロジックを追加せずに済むようにしてください。」

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解です、ファクトリーパターンで書き直してみようと思います。

def self.providers_info(ampere)
info = {
tokyo_electric: TokyoElectricPlan.new.provider_info,
loop: LoopPlan.new.provider_info
Copy link

@keishi1129 keishi1129 Dec 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あまり固有名詞をハッシュのキーにしてハードコーディングしない方がいいと思います。プランが増えるごとに修正しないといけないので。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

了解です、他の点も含めて他の設計を考えてみます。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここは動的生成で書き換えました。


class ElectricPlan
def initialize(provider)
@data = InitializeData.new(provider)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

プランの数だけYAMLがロードされてしまいませんか?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providerを入れたら、基本料金や従量料金がinitializeされるという流れになっていますが本当にそうでしょうか。
東京電力さんは複数のプランがあり、それぞれ基本料金や従量料金が違いますよね。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

重複ロードの部分はキャッシュで書き直しました。

initialize部分は迷走してるのでもう少々お待ちを。

private

def prepare_provider_data(provider)
load_provider(provider)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なぜload_providerはここで呼び出すのでしょうか。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_providersで複数のプロバイダーが混合してる状態のものを取り出し、
load_providerでプロバイダーごとに情報を分け、
prepare_provider_dataでプロバイダーごとに情報を整形、
のつもりでしたがこだわりはないので他の方法考えてみますね。

return false
end
true
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このくらいのバリデーションであればactivemodelのvalidationを使った方がいいのかなと思うのですが、このようにすると何がメリットとしてありますか?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここについては単にrailsの知識の無さからきてるものなので特に意図があったわけではありません。勉強して書き直します。

end

def load_provider(provider)
@provider_data = @providers_data[provider]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prepared_dataとに似ていて紛らわしい気がしました。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

名前が紛らわしいというのもありますが、データの出どころだったり使い分けの仕方だったりが不明確な気がしました。他のエンジニアがprepared_dataからではなく@provider_dataからデータを取ってくる実装をしてきそうだなと思いましたが、多分そういう意図ではなくprepared_dataから必ず最終outputを出したいんですよね?

であれば命名やデータの作り方を少し工夫したいですね。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_providersで複数のプロバイダーが混合してる状態のものを取り出し、
load_providerでプロバイダーごとに情報を分け、
prepare_provider_dataでプロバイダーごとに情報を整形、
のつもりでしたがこだわりはないので他の方法考えてみますね。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おっしゃる通り、時間空いて見返したら自分以外が見たら直感的じゃないなと感じました。
不要な細分化をまとめて書き直してみます。


def generate_tiers
@provider_data['tiers'].transform_keys do |key|
['Infinity', '.inf'].include?(key) ? Float::INFINITY : key

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Infinity' なのか '.inf' なのかってどこで決まりますか?
揃えられるなら揃えてしまった方が、脳に優しい気がしますがいかがでしょうか。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.infで渡ってくる場合にも対応できるように合わせて書いた事があったのでそのまま前回と同じ書き方をしてしまってました。今回のデータでは全てInfinityなので書き換えます。

{
basic_charges: generate_basic_charges,
tiers: generate_tiers,
**{ provider => generate_plan }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここって以下の方が直感的じゃないですか?

plan: generate_plan

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですね、書き直します。


class ElectricPlan
def initialize(provider)
@data = InitializeData.new(provider)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providerを入れたら、基本料金や従量料金がinitializeされるという流れになっていますが本当にそうでしょうか。
東京電力さんは複数のプランがあり、それぞれ基本料金や従量料金が違いますよね。

@@ -0,0 +1,51 @@
# frozen_string_literal: true

class InitializeData

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dataというのは可能な限り避けた方が良いです。具体的に何を初期化するのかに合わせて考えた方がよく、何とも名付けにくい場合は初期化するデータが雑多すぎないか、責務が広すぎないか疑った方が良いです。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、了解です!今回ymlから渡されるデータは料金プランに関係するものがメインなのでInitializeChargePlanで書き直してみようと思います。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このDockerfile作成の意図はなんですか?
(説明を見る限り、ローカルマシン上で動作させているようですが。)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これはrails newで自動作成されたのかなと。意図的に作ったものではありません。

@@ -0,0 +1,30 @@
# frozen_string_literal: true

class Validator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私はこれがModel層にある方が自然だと感じたのですが、なんでだと思いますか?
(Service層でも全然問題ないので、実装はそのままで良いです!)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、Activeモデルのバリデーション機能を使っているからでしょうか?

Comment on lines 22 to 23
@ampere = params[:ampere].to_i
@usage = params[:usage].to_i
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私はこの変換が乱暴だと思うんですが、なんでだと思いますか?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おっしゃる通りですね、ここに.to_i出来ない値が入る場合というのはエラーを出したい時かなと感じました。
今試したらhttp://127.0.0.1:3000/api/v1/plans/all/30/あで"あ"が0に変換されて意図しないレスポンスをしていたので修正します。

パラメータに文字列が渡ってきた場合のバリデーションとそのテスト追加
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants