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

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

mizzsugar
June 12, 2019
1.1k

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

mizzsugar

June 12, 2019
Tweet

More Decks by mizzsugar

Transcript

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

    View Slide

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

    View Slide

  3. ある日のこと・・・

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  7. 先輩「ぎゃふん」

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. ①テストコード書いた

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  18. ①「当月が誕生月である従業員を抽出する。」のテストケースのテストのみ書いたテスト
    がある(すでに通っている)
    ー当月誕生日である従業員が抽出されている
    ー当月誕生日でない従業員は抽出されていない

    View Slide

  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
    )

    View Slide

  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

    View Slide

  21. ②「前月の定期健康診断において,再検査が必要と判定された従業員を抽出する」のテ
    ストケースのテストを追加
    ー前月の定期健康診断を受けて再検査必要な従業員が抽出されている
    ー前月の定期健康診断を受けて再検査不要な従業員が抽出されていない
    ー前月以前に定期健身を受けた従業員は抽出されない

    View Slide

  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

    View Slide

  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
    )

    View Slide

  24. テスト用コマンド打つだけで
    結果確認できて楽!

    View Slide

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

    View Slide

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

    View Slide

  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'
    )

    View Slide

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

    View Slide

  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(

    View Slide

  30. {
    'employees': employees
    }
    status=200,
    content_type='application/json'
    )
    動きはするけれども、ひとつの場所で
    いろんなことやってて大変だなあ・・・

    View Slide

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

    View Slide

  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

    View Slide

  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'
    )

    View Slide

  34. Viewで表示する項目が変わった訳ではないので
    Viewの確認はほとんどしなくて良くなった

    View Slide

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

    View Slide

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

    View Slide