1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Python code smells 實(shí)例講解

        共 10418字,需瀏覽 21分鐘

         ·

        2022-06-10 18:09

        ↑?關(guān)注 + 星標(biāo)?,每天學(xué)Python新技能

        后臺(tái)回復(fù)【大禮包】送你Python自學(xué)大禮包


        code smells 可以理解為代碼中讓人感覺(jué)到不舒服的地方。可能是代碼規(guī)范問(wèn)題,也可能是設(shè)計(jì)上的缺陷。

        很多時(shí)候一段代碼符合基本邏輯,能夠正常運(yùn)行,并不代表它是不“丑”的。代碼中可能會(huì)存在諸如可讀性差、結(jié)構(gòu)混亂、重復(fù)代碼太多、不夠健壯等問(wèn)題。

        示例代碼

        """
        Very?advanced?Employee?management?system.
        "
        ""

        from?dataclasses?import?dataclass
        from?typing?import?List

        #?The?fixed?number?of?vacation?days?that?can?be?paid?out.
        FIXED_VACATION_DAYS_PAYOUT?=?5


        @dataclass
        class?Employee:
        ????"""Basic?representation?of?an?employee?at?the?company"""

        ????name:?str
        ????role:?str
        ????vacation_days:?int?=?25

        ????def?take_a_holiday(self,?payout:?bool)?->?None:
        ????????"""Let?the?employee?take?a?single?holiday,?or?pay?out?5?holidays."""
        ????????if?payout:
        ????????????if?self.vacation_days?????????????????raise?ValueError(
        ????????????????????f"You?don't?have?enough?holidays?left?over?for?a?payout.?\
        ????????????????????????????Remaining?holidays:?{self.vacation_days}"

        ????????????????)
        ????????????try:
        ????????????????self.vacation_days?-=?FIXED_VACATION_DAYS_PAYOUT
        ????????????????print(
        ????????????????????f"Paying?out?a?holiday.?Holidays?left:?{self.vacation_days}")
        ????????????except?Exception:
        ????????????????pass
        ????????else:
        ????????????if?self.vacation_days?????????????????raise?ValueError(
        ????????????????????"You?don't?have?any?holidays?left.?Now?back?to?work,?you!"
        ????????????????)
        ????????????self.vacation_days?-=?1
        ????????????print("Have?fun?on?your?holiday.?Don't?forget?to?check?your?emails!")


        @dataclass
        class?HourlyEmployee(Employee):
        ????"""?Employee?that's?paid?based?on?number?of?worked?hours."""

        ????hourly_rate:?float?=?50
        ????amount:?int?=?10


        @dataclass
        class?SalariedEmployee(Employee):
        ????"""Employee?that's?paid?based?on?a?fixed?monthly?salary."""

        ????monthly_salary:?float?=?5000


        class?Company:
        ????"""Represents?a?company?with?employees."""

        ????def?__init__(self)?->?None:
        ????????self.employees:?List[Employee]?=?[]

        ????def?add_employee(self,?employee:?Employee)?->?None:
        ????????self.employees.append(employee)

        ????def?find_managers(self)?->?List[Employee]:
        ????????managers?=?[]
        ????????for?employee?in?self.employees:
        ????????????if?employee.role?==?"manager":
        ????????????????managers.append(employee)
        ????????return?managers

        ????def?find_vice_presidents(self)?->?List[Employee]:
        ????????vice_presidents?=?[]
        ????????for?employee?in?self.employees:
        ????????????if?employee.role?==?"president":
        ????????????????vice_presidents.append(employee)
        ????????return?vice_presidents

        ????def?find_interns(self)?->?List[Employee]:
        ????????interns?=?[]
        ????????for?employee?in?self.employees:
        ????????????if?employee.role?==?"intern":
        ????????????????interns.append(employee)
        ????????return?interns

        ????def?pay_employee(self,?employee:?Employee)?->?None:
        ????????if?isinstance(employee,?SalariedEmployee):
        ????????????print(
        ????????????????f"Paying?employee?{employee.name}?a?monthly?salary?of?${employee.monthly_salary}"
        ????????????)
        ????????elif?isinstance(employee,?HourlyEmployee):
        ????????????print(
        ????????????????f"Paying?employee?{employee.name}?a?hourly?rate?of?\
        ????????????????????????????${employee.hourly_rate}?for?{employee.amount}?hours."

        ????????????)


        def?main()?->?None:
        ????company?=?Company()
        ????company.add_employee(SalariedEmployee(name="Louis",?role="manager"))
        ????company.add_employee(HourlyEmployee(name="Brenda",?role="president"))
        ????company.add_employee(HourlyEmployee(name="Tim",?role="intern"))
        ????print(company.find_vice_presidents())
        ????print(company.find_managers())
        ????print(company.find_interns())
        ????company.pay_employee(company.employees[0])
        ????company.employees[0].take_a_holiday(False)


        if?__name__?==?'__main__':
        ????main()

        上述代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的“員工管理系統(tǒng)”。

        • Employee 類(lèi)代表公司里的員工,有姓名、角色、假期等屬性。可以請(qǐng)假(take_a_holiday),或者單獨(dú)請(qǐng)一天,或者以 5 天為單位將假期兌換為報(bào)酬
        • HourlyEmployee 和 MonthlyEmployee 分別代表以時(shí)薪或者月薪來(lái)計(jì)算工資的員工
        • Company 類(lèi)代表公司,可以招收員工(add_employee)、返回特定角色的員工列表(如 find_managers)、發(fā)放薪資等(pay_employee

        code smells

        上面的代碼中存在著很多可以改進(jìn)的地方。

        用 Enum 類(lèi)型替代 str 作為員工的 role 屬性

        上面的 Employee 類(lèi)使用了 str 類(lèi)型來(lái)存儲(chǔ) role 屬性的值,比如用 "manager" 代表經(jīng)理,用 "intern" 代表實(shí)習(xí)生。

        company.add_employee(SalariedEmployee(name="Louis",?role="manager"))
        company.add_employee(HourlyEmployee(name="Brenda",?role="president"))
        company.add_employee(HourlyEmployee(name="Tim",?role="intern"))

        實(shí)際上 String 過(guò)于靈活,可以擁有任何含義,用來(lái)表示角色屬性時(shí)不具有足夠清晰的指向性。不同的拼寫(xiě)規(guī)則和大小寫(xiě)習(xí)慣都會(huì)導(dǎo)致出現(xiàn)錯(cuò)誤的指向,比如 "Manager""manager","vice-president""vice_president"??梢允褂?Enum 替代 str。

        from?enum?import?Enum,?auto


        class?Role(Enum):
        ????"""Employee?roles."""
        ????PRESIDENT?=?auto()
        ????VICEPRESIDENT?=?auto()
        ????MANAGER?=?auto()
        ????LEAD?=?auto()
        ????WORKER?=?auto()
        ????INTERN?=?auto()

        修改 Employee 類(lèi)中 role 屬性的定義:

        @dataclass
        class?Employee:

        ????name:?str
        ????role:?Role
        ????vacation_days:?int?=?25

        Company 類(lèi)中 find_managers 等方法也做相應(yīng)的修改:

        def?find_managers(self)?->?List[Employee]:
        ????managers?=?[]
        ????for?employee?in?self.employees:
        ????????if?employee.role?==?Role.MANAGER:
        ????????????managers.append(employee)
        ????return?managers

        main 方法中使用新的 role 創(chuàng)建員工對(duì)象:

        company.add_employee(SalariedEmployee(name="Louis",?role=Role.MANAGER))
        company.add_employee(HourlyEmployee(name="Brenda",?role=Role.VICEPRESIDENT))
        company.add_employee(HourlyEmployee(name="Tim",?role=Role.INTERN))
        消除重復(fù)代碼

        Company 類(lèi)中有一個(gè)功能是返回特定角色的員工列表,即 find_managersfind_vice_presidents、find_interns 三個(gè)方法。

        這三個(gè)方法實(shí)際上有著同樣的邏輯,卻分散在了三個(gè)不同的函數(shù)里??梢院喜⒊梢粋€(gè)方法來(lái)消除重復(fù)代碼。

        def?find_employees(self,?role:?Role)?->?List[Employee]:
        ????"""Find?all?employees?with?a?particular?role."""
        ????employees?=?[]
        ????for?employee?in?self.employees:
        ????????if?employee.role?==?role:
        ????????????employees.append(employee)
        ????return?employees

        同時(shí)將 main 函數(shù)中的 find_managers、find_vice_presidents、find_interns 都改為如下形式:

        print(company.find_employees(Role.VICEPRESIDENT))
        print(company.find_employees(Role.MANAGER))
        print(company.find_employees(Role.INTERN))
        盡量使用內(nèi)置函數(shù)

        上面版本中的 find_employees 方法,包含了一個(gè) for 循環(huán)。實(shí)際上該部分邏輯可以使用 Python 內(nèi)置的列表推導(dǎo)來(lái)實(shí)現(xiàn)。

        合理的使用 Python 內(nèi)置函數(shù)可以使代碼更短、更直觀,同時(shí)內(nèi)置函數(shù)針對(duì)很多場(chǎng)景在性能上也做了一定的優(yōu)化。

        def?find_employees(self,?role:?Role)?->?List[Employee]:????
        ????"""Find?all?employees?with?a?particular?role."""????
        ????
        ????return?[employee?for?employee?in?self.employees?if?employee.role?is?role]
        更清晰明確的變量名

        舊版本:

        @dataclass
        class?HourlyEmployee(Employee):???
        ????"""?Employee?that's?paid?based?on?number?of?worked?hours."""???
        ????
        ????hourly_rate:?float?=?50???
        ????amount:?int?=?10

        新版本:

        @dataclass
        class?HourlyEmployee(Employee):???
        ????"""?Employee?that's?paid?based?on?number?of?worked?hours."""??
        ????
        ????hourly_rate_dollars:?float?=?50??
        ????hours_worked:?int?=?10
        isinstance

        當(dāng)你在代碼的任何地方看到 isinstance 這個(gè)函數(shù)時(shí),都需要特別地加以關(guān)注。它意味著代碼中有可能存在某些有待提升的設(shè)計(jì)。

        比如代碼中的 pay_employee 函數(shù):

        def?pay_employee(self,?employee:?Employee)?->?None:???
        ????if?isinstance(employee,?SalariedEmployee):?????
        ???????print(???????
        ???????????f"Paying?employee?{employee.name}?a?monthly?salary?of?${employee.monthly_salary}"???????
        ???????)??
        ???elif?isinstance(employee,?HourlyEmployee):?????
        ???????print(?????
        ???????????f"Paying?employee?{employee.name}?a?hourly?rate?of?\?????????????????????
        ???????????????????????${employee.hourly_rate_dollars}?for?{employee.hours_worked}?hours."
        ??????
        ????????)

        這里 isinstance 的使用,實(shí)際上在 pay_employee 函數(shù)中引入了對(duì) Employee 的子類(lèi)的依賴(lài)。這種依賴(lài)導(dǎo)致各部分代碼之間的職責(zé)劃分不夠清晰,耦合性變強(qiáng)。

        pay_employee 方法需要與 Employee 的子類(lèi)的具體實(shí)現(xiàn)保持同步。每新增一個(gè)新的員工類(lèi)型(Employee 的子類(lèi)),此方法中的 if-else 也就必須再新增一個(gè)分支。即需要同時(shí)改動(dòng)不同位置的兩部分代碼。

        可以將 pay_employee 的實(shí)現(xiàn)從 Company 類(lèi)轉(zhuǎn)移到具體的 Employee 子類(lèi)中。即特定類(lèi)型的員工擁有對(duì)應(yīng)的報(bào)酬支付方法,公司在發(fā)薪時(shí)只需要調(diào)用對(duì)應(yīng)員工的 pay 方法,無(wú)需實(shí)現(xiàn)自己的pay_employee 方法。由 isinstance 引入的依賴(lài)關(guān)系從而被移除。

        @dataclass
        class?HourlyEmployee(Employee):???
        ????"""?Employee?that's?paid?based?on?number?of?worked?hours."""???
        ????
        ????hourly_rate_dollars:?float?=?50??
        ????hours_worked:?int?=?10??
        ????
        ????def?pay(self):???
        ????????print(????????
        ????????????f"Paying?employee?{self.name}?a?hourly?rate?of?\???????????????????
        ????????????????????${self.hourly_rate_dollars}?for?{self.hours_worked}?hours."
        ???????
        ?????????)
        ?????????
        @dataclass
        class?SalariedEmployee(Employee):??
        ????"""Employee?that's?paid?based?on?a?fixed?monthly?salary."""???
        ????
        ????monthly_salary:?float?=?5000??
        ????
        ????def?pay(self):???
        ????????print(????????
        ????????????f"Paying?employee?{self.name}?a?monthly?salary?of?${self.monthly_salary}"?????
        ????????)

        再把 main 函數(shù)中的 company.pay_employee(company.employees[0]) 改為 company.employees[0].pay()

        由于每一個(gè)特定的 Employee 子類(lèi)都需要實(shí)現(xiàn) pay 方法,更好的方式是將 Employee 實(shí)現(xiàn)為虛擬基類(lèi),pay 成為子類(lèi)必須實(shí)現(xiàn)的虛擬方法。

        from?abc?import?ABC,?abstractmethod


        class?Employee(ABC):??

        ?????@abstractmethod???
        ?????def?pay()?->?None:?????
        ?????????"""Method?to?call?when?paying?an?employee"""
        Bool flag

        Employee 類(lèi)中的 take_a_holiday 方法有一個(gè)名為 payout 的參數(shù)。它是布爾類(lèi)型,作為一個(gè)開(kāi)關(guān),來(lái)決定某個(gè)員工是請(qǐng)一天假,還是以 5 天為單位將假期兌換為報(bào)酬。

        這個(gè)開(kāi)關(guān)實(shí)際上導(dǎo)致了 take_a_holiday 方法包含了兩種不同的職責(zé),只通過(guò)一個(gè)布爾值來(lái)決定具體執(zhí)行哪一個(gè)。

        函數(shù)原本的目的就是職責(zé)的分離。使得同一個(gè)代碼塊中不會(huì)包含過(guò)多不同類(lèi)型的任務(wù)。

        因此 take_a_holiday 方法最好分割成兩個(gè)不同的方法,分別應(yīng)對(duì)不同的休假方式。

        class?Employee(ABC):???

        ????def?take_a_holiday(self)?->?None:???
        ????????"""Let?the?employee?take?a?single?holiday."""???
        ????????
        ????????if?self.vacation_days?????????????raise?ValueError(?????????
        ????????????????"You?don't?have?any?holidays?left.?Now?back?to?work,?you!"?????????
        ????????????)??????
        ????????self.vacation_days?-=?1????
        ????????print("Have?fun?on?your?holiday.?Don't?forget?to?check?your?emails!")???
        ????def?payout_a_holiday(self)?->?None:????
        ????????"""Let?the?employee?get?paid?for?unused?holidays."""???????
        ????????
        ????????if?self.vacation_days????????????raise?ValueError(?????????????
        ???????????????f"You?don't?have?enough?holidays?left?over?for?a?payout.?\?????????????????????
        ???????????????????????Remaining?holidays:?{self.vacation_days}"
        ??????????
        ????????????)????
        ????????try:?????????
        ????????????self.vacation_days?-=?FIXED_VACATION_DAYS_PAYOUT???????????
        ????????????print(??????????
        ????????????????f"Paying?out?a?holiday.?Holidays?left:?{self.vacation_days}")??????
        ?????????except?Exception:??????
        ?????????????pass
        Exceptions

        payout_a_holiday 方法中有一步 try-except 代碼。但該部分代碼實(shí)際上對(duì) Exception 沒(méi)有做任何事。對(duì)于 Exception 而言:

        如果需要 catch Exception,就 catch 特定類(lèi)型的某個(gè) Exception,并對(duì)其進(jìn)行處理;如果不會(huì)對(duì)該 Exception 做任何處理,就不要 catch 它

        在此處使用 try-except 會(huì)阻止異常向外拋出,導(dǎo)致外部代碼在調(diào)用 payout_a_holiday 時(shí)獲取不到異常信息。此外,使用 Exception 而不是某個(gè)特定類(lèi)型的異常,會(huì)導(dǎo)致所有的異常信息都被屏蔽掉,包括語(yǔ)法錯(cuò)誤、鍵盤(pán)中斷等。

        因此,去掉上述代碼中的 try-except

        使用自定義 Exception 替代 ValueError

        ValueError 是 Python 內(nèi)置的在內(nèi)部出現(xiàn)值錯(cuò)誤時(shí)拋出的異常,并不適合用在自定義的場(chǎng)景中。最好在代碼中定義自己的異常類(lèi)型。

        class?VacationDaysShortageError(Exception):??
        ????"""Custom?error?that?is?raised?when?not?enough?vacation?days?available."""???
        ????
        ????def?__init__(self,?requested_days:?int,?remaining_days:?int,?message:?str)?->?None:?????
        ????????self.requested_days?=?requested_days???
        ????????self.remaining_days?=?remaining_days????
        ????????self.message?=?message????
        ????????super().__init__(message)
        def?payout_a_holiday(self)?->?None:?
        ????"""Let?the?employee?get?paid?for?unused?holidays."""??
        ????
        ????if?self.vacation_days?????????raise?VacationDaysShortageError(????
        ????????????requested_days=FIXED_VACATION_DAYS_PAYOUT,???
        ????????????remaining_days=self.vacation_days,?????
        ????????????message="You?don't?have?enough?holidays?left?over?for?a?payout.")??
        ????self.vacation_days?-=?FIXED_VACATION_DAYS_PAYOUT??
        ????print(f"Paying?out?a?holiday.?Holidays?left:?{self.vacation_days}")

        最終版本

        """
        Very?advanced?Employee?management?system.
        "
        ""

        from?dataclasses?import?dataclass
        from?typing?import?List
        from?enum?import?Enum,?auto
        from?abc?import?ABC,?abstractmethod

        #?The?fixed?number?of?vacation?days?that?can?be?paid?out.
        FIXED_VACATION_DAYS_PAYOUT?=?5


        class?VacationDaysShortageError(Exception):
        ????"""Custom?error?that?is?raised?when?not?enough?vacation?days?available."""

        ????def?__init__(self,?requested_days:?int,?remaining_days:?int,?message:?str)?->?None:
        ????????self.requested_days?=?requested_days
        ????????self.remaining_days?=?remaining_days
        ????????self.message?=?message
        ????????super().__init__(message)


        class?Role(Enum):
        ????"""Employee?roles."""
        ????PRESIDENT?=?auto()
        ????VICEPRESIDENT?=?auto()
        ????MANAGER?=?auto()
        ????LEAD?=?auto()
        ????WORKER?=?auto()
        ????INTERN?=?auto()


        @dataclass
        class?Employee(ABC):
        ????"""Basic?representation?of?an?employee?at?the?company"""

        ????name:?str
        ????role:?Role
        ????vacation_days:?int?=?25

        ????def?take_a_holiday(self)?->?None:
        ????????"""Let?the?employee?take?a?single?holiday."""

        ????????if?self.vacation_days?????????????raise?VacationDaysShortageError(
        ????????????????requested_days=1,
        ????????????????remaining_days=self.vacation_days,
        ????????????????message="You?don't?have?any?holidays?left.?Now?back?to?work,?you!")
        ????????self.vacation_days?-=?1
        ????????print("Have?fun?on?your?holiday.?Don't?forget?to?check?your?emails!")

        ????def?payout_a_holiday(self)?->?None:
        ????????"""Let?the?employee?get?paid?for?unused?holidays."""

        ????????if?self.vacation_days?????????????raise?VacationDaysShortageError(
        ????????????????requested_days=FIXED_VACATION_DAYS_PAYOUT,
        ????????????????remaining_days=self.vacation_days,
        ????????????????message="You?don't?have?enough?holidays?left?over?for?a?payout."
        ????????????)
        ????????self.vacation_days?-=?FIXED_VACATION_DAYS_PAYOUT
        ????????print(f"Paying?out?a?holiday.?Holidays?left:?{self.vacation_days}")

        ????@abstractmethod
        ????def?pay()?->?None:
        ????????"""Method?to?call?when?paying?an?employee"""


        @dataclass
        class?HourlyEmployee(Employee):
        ????"""?Employee?that's?paid?based?on?number?of?worked?hours."""

        ????hourly_rate_dollars:?float?=?50
        ????hours_worked:?int?=?10

        ????def?pay(self):
        ????????print(
        ????????????f"Paying?employee?{self.name}?a?hourly?rate?of?\
        ????????????????????${self.hourly_rate_dollars}?for?{self.hours_worked}?hours."

        ????????)


        @dataclass
        class?SalariedEmployee(Employee):
        ????"""Employee?that's?paid?based?on?a?fixed?monthly?salary."""

        ????monthly_salary:?float?=?5000

        ????def?pay(self):
        ????????print(
        ????????????f"Paying?employee?{self.name}?a?monthly?salary?of?${self.monthly_salary}"
        ????????)


        class?Company:
        ????"""Represents?a?company?with?employees."""

        ????def?__init__(self)?->?None:
        ????????self.employees:?List[Employee]?=?[]

        ????def?add_employee(self,?employee:?Employee)?->?None:
        ????????self.employees.append(employee)

        ????def?find_employees(self,?role:?Role)?->?List[Employee]:
        ????????"""Find?all?employees?with?a?particular?role."""

        ????????return?[employee?for?employee?in?self.employees?if?employee.role?is?role]


        def?main()?->?None:
        ????company?=?Company()
        ????company.add_employee(SalariedEmployee(name="Louis",?role=Role.MANAGER))
        ????company.add_employee(HourlyEmployee(
        ????????name="Brenda",?role=Role.VICEPRESIDENT))
        ????company.add_employee(HourlyEmployee(name="Tim",?role=Role.INTERN))
        ????print(company.find_employees(Role.VICEPRESIDENT))
        ????print(company.find_employees(Role.MANAGER))
        ????print(company.find_employees(Role.INTERN))
        ????company.employees[0].pay()
        ????company.employees[0].take_a_holiday()


        if?__name__?==?'__main__':
        ????main()

        參考資料

        7 Python Code Smells: Olfactory Offenses To Avoid At All Costs

        來(lái)源:

        https://www.starky.ltd/2021/12/06/7-python-code-smells-by-practical-example/


        1. 【Python練習(xí)題】吃透這150道練習(xí)題,輕松搞定Python95%知識(shí)點(diǎn) (含答案解析)

        2. 一些著名的軟件都用什么語(yǔ)言編寫(xiě)?

        3. 知乎熱問(wèn):國(guó)家何時(shí)整治程序員的高薪現(xiàn)象?


        瀏覽 46
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲国产精品国自产拍张津瑜 | 欧美一级久久久久久久 | 好吊视频一区二区三区 | 忘忧草精品久久久久久久高清 | 亚洲AV电影在线观看 | 久久色网 | 国产嫩草影院在线观看 | 久久人妻无码毛片A片麻豆 | 人人色视频 | 狠狠色狠狠色综合人人 |