Upgrade to Pro — share decks privately, control downloads, hide ads and more …

新人の私が_君_修正早いね_と言ってもらえた話.pdf

C98d379da6e5517afff697a6c5615e68?s=47 mizzsugar
June 12, 2019
900

 新人の私が_君_修正早いね_と言ってもらえた話.pdf

C98d379da6e5517afff697a6c5615e68?s=128

mizzsugar

June 12, 2019
Tweet

Transcript

  1. 職業Pythonista歴半年の私が「君、 修正早いね」 先輩を唸らせた話 2019/06/12 @Stapy mizzsugar0425

  2. お前誰よ ・みずきと申します ・2019/01~ 職業Pythonista(Djangoで受託開発してます。案件 募集中!!!) ・好き→Python/Django/Pyramid/React/PostgreSQL/コーヒー/ ロードバイク/一眼レフ

  3. ある日のこと・・・

  4. 上司に仕様変更を伝えられる

  5. それを聞いた先輩「これはエグい変更ですね」

  6. しかし30分程で変更を実装した私

  7. 先輩「ぎゃふん」

  8. YOU LOSE!! 俺の勝ち! 戦ってないけど

  9. 速攻ポイント ①テストコード書いた ②疎結合な実装を試みた

  10. 仕事のコード見せるわけにもいかないので FEの問題を参考にしました 仕様書 https://www.fe-siken.com/kakomon/30_haru/pm05.html 今回書いた詳しいコード(まだ途中) https://github.com/mizzsugar/medical-checkup

  11. 健診管理システムでこんな仕様があったとして https://www.fe-siken.com/kakomon/30_haru/pm05.html より 1. 当月が誕生月である従業員を抽出する。 2. ①で抽出した従業員を受診対象者とし,健康診断コースを決定する。 3. 受診日を決定し,受診対象者の健康診断レコードを健康管理システ ムに登録する。

  12. 突如打ち込まれる新たな手順 https://www.fe-siken.com/kakomon/30_haru/pm05.html より 1. 当月が誕生月である従業員を抽出する。 2. 前月の定期健康診断において,再検査が必要と判定された従業員を抽出 する。 3. ①と②で抽出した従業員を受診対象者とし,健康診断コースを決定する。 4.

    受診日を決定し,受診対象者の健康診断レコードを健康管理システムに登 録する。
  13. 突如打ち込まれる新たな手順 https://www.fe-siken.com/kakomon/30_haru/pm05.html より 1. 当月が誕生月である従業員を抽出する。 2. 前月の定期健康診断において,再検査が必要と判定された従業員を抽出 する。 3. ①と②で抽出した従業員を受診対象者とし,健康診断コースを決定する。 4.

    受診日を決定し,受診対象者の健康診断レコードを健康管理システムに登 録する。 さあ、 どうする!?!?
  14. ①テストコード書いた

  15. テストコード書かないと  プロダクトコードを修正 →ブラウザ開いて画面ぽちぽちして動作確認 →エラーになったらエラーの該当箇所を確認 →プロダクトコードを修正 →ブラウザ開いて画面ぽちぽちして動作確認 ・・・

  16. テストコード書かないと  プロダクトコードを修正 →ブラウザ開いて画面ぽちぽちして動作確認 →エラーになったらエラーの該当箇所を確認 →プロダクトコードを修正 →ブラウザ開いて画面ぽちぽちして動作確認 ・・・ 大変!!!!(>_<)

  17. テストコード書くと ①「当月が誕生月である従業員を抽出する。」のテストケースのテストのみ書いたテスト がある(すでに通っている) ー当月誕生日である従業員が抽出されている ー当月誕生日でない従業員は抽出されていない ②「前月の定期健康診断において,再検査が必要と判定された従業員を抽出する」のテ ストケースのテストを追加 ー前月の定期健康診断を受けて再検査必要な従業員が抽出されている ー前月の定期健康診断を受けて再検査不要な従業員が抽出されていない ー前月以前に定期健身を受けた従業員は抽出されない

    ③テスト回す→落ちる→プロダクトコード修正→通る→リファクタリング
  18. ①「当月が誕生月である従業員を抽出する。」のテストケースのテストのみ書いたテスト がある(すでに通っている) ー当月誕生日である従業員が抽出されている ー当月誕生日でない従業員は抽出されていない

  19. class TestExtractExamees(TestCase): @classmethod def setUpTestData(cls): """テスト用のデータを作成 長いので省略 """ ... def

    test_iter_birthday_month_employees(self): actual = medical_checkup.core.extract_examinee.iter_birthday_month_employees(today=datetime.date(2019, 5, 1)) actual_list = list(actual) with self.subTest('5月が誕生日である社員がリストに入ってる'): self.assertTrue( employee.types.Employee( id=self.emp_1.id, birthday=datetime.date(1990, 5, 10), gender=employee.types.Gender(1), is_manager=False ) and employee.types.Employee( id=self.emp_2.id, birthday=datetime.date(1980, 5, 11), gender=employee.types.Gender(0), is_manager=True ) in actual_list )
  20. ) with self.subTest('5月が誕生日でない社員がリストに入っている'): self.assertFalse( employee.types.Employee( id=self.emp_3.id, birthday=datetime.date(1980, 6, 11), gender=employee.types.Gender(0),

    is_manager=True ) in actual_list ) self.assertFalse( employee.types.Employee( id=self.emp_4.id, birthday=datetime.date(1980, 6, 12), gender=employee.types.Gender(1), is_manager=True ) in actual_list ) @classmethod def tearDownClass(cls): pass
  21. ②「前月の定期健康診断において,再検査が必要と判定された従業員を抽出する」のテ ストケースのテストを追加 ー前月の定期健康診断を受けて再検査必要な従業員が抽出されている ー前月の定期健康診断を受けて再検査不要な従業員が抽出されていない ー前月以前に定期健身を受けた従業員は抽出されない

  22. def test_iter_reexamine_employees(self): # 前月(このテストでは2019年5月)の定期健康診断において,再検査が必要と判定された従業員のみがリストに入っている actual = medical_checkup.core.extract_examinee.iter_reexamine_employees( conducted_year=2019, conducted_month=5 )

    actual_list = list(actual) with self.subTest('前月の定期健康診断を受けて再検査必要な従業員が抽出されている'): self.assertTrue( employee.types.Employee( id=self.emp_5.id, birthday=datetime.date(1980,5,11), gender=employee.types.Gender(0), is_manager=True ) in actual_list ) with self.subTest('前月の定期健康診断を受けて再検査不要な従業員が抽出されていない'): self.assertTrue( employee.types.Employee( id=self.emp_6.id, birthday=datetime.date(1980,5,11), gender=employee.types.Gender(0), is_manager=True ) in actual_list
  23. gender=employee.types.Gender(0), is_manager=True ) in actual_list ) with self.subTest('前月の定期健康診断を受けて再検査不要な従業員が抽出されていない'): self.assertFalse( employee.types.Employee(

    id=self.emp_6.id, birthday=datetime.date(1980,5,11), gender=employee.types.Gender(0), is_manager=True ) in actual_list ) with self.subTest('前月以前に定期健身を受けた従業員は抽出されない'): self.assertFalse( employee.types.Employee( id=self.emp_7.id, birthday=datetime.date(1980,5,11), gender=employee.types.Gender(0), is_manager=True ) in actual_list )
  24. テスト用コマンド打つだけで 結果確認できて楽!

  25. ②疎結合な実装を試みた

  26. ここを実装するとします https://www.fe-siken.com/kakomon/30_haru/pm05.html より 1. 当月が誕生月である従業員を抽出する。 2. ①で抽出した従業員を受診対象者とし,健康診断コースを決定する。 3. 受診日を決定し,受診対象者の健康診断レコードを健康管理システ ムに登録する。

  27. Viewに全てをまとめるとこうなります。 import datetime from django.http import HttpRespons import employee.models.employee def

    show_examiees_fat_controller(request): examinees = [ { 'id': emp.id, 'birthday': emp.birthday } for emp in employee.models.employee.Employee.objects.all() if emp.birthday.month==datetime.date.today().month ] return HttpResponse( { 'examinees': examinees } status=200, content_type='application/json' )
  28. 追加されます https://www.fe-siken.com/kakomon/30_haru/pm05.html より 1. 当月が誕生月である従業員を抽出する。 2. 前月の定期健康診断において,再検査が必要と判定された従業員を抽出 する。 3. ①と②で抽出した従業員を受診対象者とし,健康診断コースを決定する。 4.

    受診日を決定し,受診対象者の健康診断レコードを健康管理システムに登 録する。
  29. def show_examiees_fat_controller(request): # 当月誕生日の人 employees = [ { 'id': emp.id,

    'birthday': emp.birthday } for emp in employee.models.employee.Employee.objects.all() if emp.birthday.month==datetime.date.today().month ] # 前月を定義 last_month = datetime.date.today() - relativedelta(month=1) # 前月の検査で再検査が必要と言われた人 re_exam_employees = [ { 'id': mc.employee.id, 'birthday': mc.employee.birthday } for mc in medical_checkup.models.checkup.MedicalCheckUp.objecsts.all() if mc.conducted_year==last_month.year and mc.conducted_month==last_month.month and mc.need_reexamination ] # 当月誕生日の人と前月の検査で再検査が必要と言われた人の配列を結合 employees += re_exam_employees return HttpResponse(
  30. { 'employees': employees } status=200, content_type='application/json' ) 動きはするけれども、ひとつの場所で いろんなことやってて大変だなあ・・・

  31. viewと先ほどの計算部分のモジュールを分割すると

  32. # (1)1 当月が誕生月である従業員を抽出する。 def iter_birthday_month_employees(today: datetime.date)->Iterator[employee.types.Employee]: """ today: 基本的にはdatetime.date.today()が入ります """

    return ( emp for emp in employee.models.employee.Manager.iter_all() if emp.birthday.month==today.month ) # (1)2 前月の定期健康診断において,再検査が必要と判定された従業員を抽出する。 def iter_reexamine_employees(conducted_year: int, conducted_month: int) -> Iterator[employee.types.Employee]: return ( mc.employee for mc in medical_checkup.models.checkup.Manager.iter_all() if mc.conducted_year==conducted_year and mc.conducted_month==conducted_month and mc.need_reexamination ) # (1)と(2)を合わせたメソッド。View関数内にはこちらをいれる def extract_examiee(today: datetime.date) -> Iterator[employee.types.Employee]: last_month = today - relativedelta(month=1) conducted_year = last_month.year conducted_month = last_month.month return itertools.chain( iter_birthday_month_employees((today)), iter_reexamine_employees(conducted_year, conducted_year) ) service.py
  33. views.py def show_examiees_fat_controller(request): # 対象者を抽出するメソッド employees = [ asdict(emp) for

    emp in extract_examiee(datetime.date.today()) ] return HttpResponse( { 'employees': employees } status=200, content_type='application/json' )
  34. Viewで表示する項目が変わった訳ではないので Viewの確認はほとんどしなくて良くなった

  35. まとめ ・テストコードで楽々動作確認 ・疎結合で修正箇所を小さく抑える

  36. ありがとうございました!!!