<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      為什么構造函數需要盡可能的簡單

          最近在做一些代碼重構,涉及到Python中部分代碼重構后,單元測試實現較為麻煩甚至難以實現的場景,其中一個主要的原因是構造函數過于復雜。

      因此,本篇文章借此總結一下我們應該需要什么樣的構造函數。本篇文章涉及的概念不僅限于Python。

       

      構造函數是什么

      構造函數用于創建對象時觸發,如果不自定義構造函數,通常現代的編程語言在編譯時會自動加一個無參的構造函數,同時將類成員設置成默認值,Python中需要定義對象成員才能訪問,而類C語言比如C#中int、bool、float等都會設置為0等值的值,比如整數為0、浮點數為0.0或布爾值為false,對于非原始值的引用類型,比如String或Class,都會設置為Null。

      構造函數是對類初始化非常合理的位置。因為構造函數是新建對象時觸發,相比對象構造之后再去修改對象屬性,帶來的麻煩遠比收益多,比如說空指針、時序耦合、多線程問題等,這些有興趣后續有機會再聊,但總之將類的初始化放到構造函數中就像是先打地基再蓋房子,而不是房子蓋一半再回頭修補地基,也避免類處于“半成品”狀態。

      雖然構造函數應該做完整的工作避免半成品,但如果給構造函數賦予太多的責任,會對系統帶來很多麻煩,就好比房子主體結構(構造函數)還沒完工,就要搬家具進房屋,通常會帶來不必要的負擔。

       

      我們需要什么樣的構造函數

      一句話總結:在我看來,構造函數只應該做賦值,以及最基本的參數校驗。而不應該做外部調用和復雜的初始化,使用簡單構造函數能夠帶來如下好處:

       

      可維護性

      單一職責,避免驚喜

      構造函數也應當遵循單一職責原則,僅負責對象的初始化和基本驗證,而不應包含其他復雜操作。當構造函數承擔過多責任時,會產生意外的"驚喜",使代碼難以理解和維護。

      例如下面代碼,在構造函數中執行了數據庫查詢操作(外部依賴),以及統計計算(無外部依賴,復雜的內部計算),我們很難一眼看出該函數初始化要做什么,增加閱讀和理解代碼的認知負擔。

      class UserReport:
          def __init__(self, user_id):
              self.user_id = user_id
              # 構造函數中進行數據庫操作(有外部依賴)
              self.user = database.fetch_user(user_id)
              # 構造函數中執行復雜計算(內部復雜計算,無外部依賴)
              self.statistics = self._calculate_statistics()
          
          def _calculate_statistics(self):
              # 假設是一個復雜的統計計算
              return {"login_count": 42, "active_days": 15}

      而理想的構造函數,應該只是簡單做“初始化賦值”這一個操作,如下所示:

      class UserReport:
          def __init__(self, user, statistics):
              """構造函數只負責初始化,不執行其他操作"""
              self.user = user
              self.statistics = statistics

      該構造函數只做初始化賦值,沒有預期之外的情況,比如例子中_calculate_statistics函數,如果在方法內繼續引用其他類,其他類再次有外部依賴的訪問(比如IO、API調用、數據庫操作等),會產生驚喜。

       

      減少意外的副作用

      構造函數中包含復雜操作不僅違反單一職責原則,還可能帶來意外的副作用。這些副作用可能導致系統行為不可預測,增加調試難度,甚至引發難以察覺的bug。

      我們繼續看之前的代碼示例:

      class UserReport:
          def __init__(self, user_id):
              self.user_id = user_id
              # 構造函數中進行數據庫操作
              self.user = database.fetch_user(user_id)
              # 構造函數中執行復雜計算
              self.statistics = self._calculate_statistics()
          
          def _calculate_statistics(self):
              # 復雜的統計計算
              data = database.fetch_user_activities(self.user_id)
              if not data:
                  # 可能拋出異常
                  raise ValueError(f"No activity data for user {self.user_id}")
              return {"login_count": len(data), "active_days": len(set(d.date() for d in data))}

      這段代碼可以看到,_calculate_statistics() 函數有數據庫訪問,這是隱藏的依賴,同時如果數據庫訪問存在異常可能導致整個對象創建失敗,調用者只想創建對象,卻可能引發了數據庫無法連接的異常。這在運行時都屬于意外。

      Traceback (most recent call last):
        File "main.py", line 42, in <module>
          report = UserReport(user_id=1001)  # 調用者只是想創建一個報告對象
        File "user_report.py", line 5, in __init__
          self.user = database.fetch_user(user_id)  # 數據庫查詢可能失敗
        File "database.py", line 78, in fetch_user
          user_data = self._execute_query(f"SELECT * FROM users WHERE id = {user_id}")
        File "database.py", line 31, in _execute_query
          connection = self._get_connection()
        File "database.py", line 15, in _get_connection
          return pymysql.connect(host=self.host, user=self.user, password=self.password, db=self.db_name)
        File "/usr/local/lib/python3.8/site-packages/pymysql/__init__.py", line 94, in Connect
          return Connection(*args, **kwargs)
        File "/usr/local/lib/python3.8/site-packages/pymysql/connections.py", line 327, in __init__
          self.connect()
        File "/usr/local/lib/python3.8/site-packages/pymysql/connections.py", line 629, in connect
          raise exc
      pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on 'db.example.com' (timed out)")

      而將計算邏輯提取到專門函數,訪問外部依賴的邏輯通過注入進行,就不會存在該問題:

      class UserReport:
          def __init__(self, user, statistics=None):
              """構造函數只負責初始化,無副作用"""
              self.user = user
              self.statistics = statistics if statistics is not None else {}
          
          def calculate_statistics(self, activity_source):
              """將計算邏輯分離到專門的方法,并接受依賴注入"""
              activities = activity_source.get_activities(self.user.id)
              self.statistics = {
                  "login_count": len(activities),
                  "active_days": len(set(a.date for a in activities))
              }
              return self.statistics
      
      class UserActivity:
          def __init__(self, user_id, date, action):
              self.user_id = user_id
              self.date = date
              self.action = action
      
      class DatabaseActivity:
          def get_activities(self, user_id):
              # 實際應用中會查詢數據庫
              return database.fetch_user_activities(user_id)

       

      方便調試和演進

      構造函數僅負責簡單的初始化時,代碼變得更加易于調試和演進。相比之下,包含復雜邏輯的構造函數會使問題定位和系統擴展變得困難。比如下面例子

      class UserReport:
          def __init__(self, user_id):
              self.user_id = user_id
              self.user = database.fetch_user(user_id)
              self.activities = database.fetch_user_activities(user_id)
              self.statistics = self._calculate_statistics()
              self.recommendations = self._generate_recommendations()
              # 更多復雜邏輯...

      可以看到構造函數包括了太多可能失敗的點,調試時也不容易找到具體哪一行除了問題。而下面方式調試容易很多:

      class UserReport:
          def __init__(self, user, activities=None, statistics=None, recommendations=None):
              self.user = user
              self.activities = activities or []
              self.statistics = statistics or {}
              self.recommendations = recommendations or []

      而演進時,復雜的構造函數有很大風險,例如:

      # 需要修改原有構造函數,風險很高
      class UserReport:
          def __init__(self, user_id, month=None):  # 添加新參數
              self.user_id = user_id
              self.user = database.fetch_user(user_id)
              # 修改現有邏輯
              if month:
                  self.activities = database.fetch_user_activities_by_month(user_id, month)
              else:
                  self.activities = database.fetch_user_activities(user_id)
              # 以下計算可能需要調整
              self.statistics = self._calculate_statistics()
              self.recommendations = self._generate_recommendations()

      我們需要添加按月篩選活動數據,增加一個參數,這種情況也是實際代碼維護中經常出現的,想到哪寫到哪,導致構造函數變的非常復雜難以理解,同時增加出錯可能性,而更好的方式如下:

      class UserReport:
          def __init__(self, user, activities=None, statistics=None, recommendations=None):
              self.user = user
              self.activities = activities or []
              self.statistics = statistics or {}
              self.recommendations = recommendations or []
              
          def filter_by_month(self, month):
              """添加新功能作為單獨的方法"""
              filtered_activities = [a for a in self.activities if a.date.month == month]
              return UserReport(
                  self.user,
                  activities=filtered_activities,
                  # 可根據需要重新計算或保留原有數據
              )

      新功能可以獨立添加,不影響現有功能,同時也避免修改這種核心邏輯時測試不全面帶來的上線提心吊膽。

       

      可測試性

      良好的構造函數設計對代碼的可測試性有著決定性的影響。當構造函數簡單且只負責基本初始化時,測試變得更加容易、更加可靠,且不依賴于特定環境。這也是為什么我寫本篇文章的原因,就是在寫單元測試時發現很多類幾乎不可測試(部分引用的第三方類庫中的類,類本身屬于其他組件,我無權修改,-.-)。

      依賴注入與可測試性

      如果構造函數有較多邏輯,例如:

      class UserReport:
          def __init__(self, user_id):
              self.user_id = user_id
              self.user = database.fetch_user(user_id)
              self.activities = database.fetch_user_activities(user_id)
              self.statistics = self._calculate_statistics()

      那么我們的單元測試會變的成本非常高昂,每一個外部依賴都需要mock,就算只需要測試一個非常簡單的Case,也需要模擬所有外部依賴,比如

      def test_user_report():
          # 需要大量的模擬設置
          with patch('module.database.fetch_user') as mock_fetch_user:
              with patch('module.database.fetch_user_activities') as mock_fetch_activities:
                  # 配置模擬返回值
                  mock_fetch_user.return_value = User(1, "Test User", "test@example.com")
                  mock_fetch_activities.return_value = [
                      Activity(1, datetime(2023, 1, 1), "login"),
                      Activity(1, datetime(2023, 1, 2), "login")
                  ]
                  
                  # 創建對象 - 即使只是測試一小部分功能也需要模擬所有依賴
                  report = UserReport(1)
                  
                  # 驗證結果
                  assert report.statistics["login_count"] == 2
                  assert report.statistics["active_days"] == 2
                  
                  # 驗證調用
                  mock_fetch_user.assert_called_once_with(1)
                  mock_fetch_activities.assert_called_once_with(1)

       

      而構造函數簡單,我們的單元測試也會變得非常簡單,比如針對下面代碼進行測試:

      class UserReport:
          def __init__(self, user, activities=None):
              self.user = user
              self.activities = activities or []
              self.statistics = {}
          
          def calculate_statistics(self):
              """計算統計數據"""
              login_count = len(self.activities)
              active_days = len(set(a.date for a in self.activities))
              self.statistics = {
                  "login_count": login_count,
                  "active_days": active_days
              }
              return self.statistics

      可以看到單元測試不再需要復雜的Mock

      def test_report_should_calculate_correct_statistics_when_activities_provided():
          # 直接創建測試對象,無需模擬外部依賴
          user = User(1, "Test User", "test@example.com")
          activities = [
              UserActivity(1, datetime(2023, 1, 1), "login"),
              UserActivity(1, datetime(2023, 1, 2), "login"),
              UserActivity(1, datetime(2023, 1, 2), "logout")  # 同一天的另一個活動
          ]
          
          # 創建對象非常簡單
          report = UserReport(user, activities)
          
          # 測試特定方法
          stats = report.calculate_statistics()
          
          # 驗證結果
          assert stats["login_count"] == 3
          assert stats["active_days"] == 2

      同時測試時,Mock對象注入也變得非常簡單,如下:

      def test_report_should_use_activity_source_when_calculating_statistics():
          # 準備測試數據
          user = User(42, "Test User", "test@example.com")
          mock_activities = [
              UserActivity(42, datetime(2023, 1, 1), "login"),
              UserActivity(42, datetime(2023, 1, 2), "login")
          ]
          
          # 創建模擬數據源
          activity_source = MockActivity(mock_activities)
          
          # 使用依賴注入
          report = UserReport(user)
          report.calculate_statistics(activity_source)
          
          # 驗證結果
          assert report.statistics["login_count"] == 2
          assert report.statistics["active_days"] == 2

      而做邊界值測試時更為簡單:

      def test_statistics_should_be_empty_when_activities_list_is_empty():
          user = User(1, "Test User", "test@example.com")
          report = UserReport(user, [])  # 空活動列表
          
          stats = report.calculate_statistics()
          assert stats["login_count"] == 0
          assert stats["active_days"] == 0
      
      def test_constructor_should_throw_exception_when_user_is_null():
          # 測試無效用戶情況
          with pytest.raises(ValueError):
              report = UserReport(None)  # 假設我們在構造函數中驗證用戶不為空

      因此整個代碼邏輯通過單元測試將變得更為健壯,而不是需要大量復雜的Mock,復雜的Mock會導致單元測試非常脆弱(也就是修改一點邏輯,導致現有的單元測試無效)

       

      架構相關影響

      更容易依賴注入

      依賴注入的核心理念是高層模塊不應該依賴于低層模塊的實現細節,而應該依賴于抽象。好比我們需要打車去公司上班,我們只要打開滴滴輸入目的地,我們更高層次的需求是從A到B,而具體的實現細節是打車過程是哪款車,或者司機是誰,這也不是我們關心的。具體由哪輛車,哪位司機提供服務可以隨時切換。

      依賴注入是現代軟件架構的核心實踐之一,而簡單的構造函數設計是實現有效依賴注入的基礎。通過構造函數注入依賴,我們可以構建松耦合、高內聚的系統,顯著提高代碼的可維護性和可擴展性。

      # 直接在類內部創建依賴
      class UserReport:
          def __init__(self, user_id):
              self.user_id = user_id
              # 直接依賴具體實現
              self.database = MySQLDatabase()
              self.user = self.database.fetch_user(user_id)
      # 通過構造函數注入依賴
      class UserReport:
          def __init__(self, user, activity_source):
              self.user = user
              self.activity_source = activity_source
              self.statistics = {}
          
          def calculate_statistics(self):
              activities = self.activity_source.get_activities(self.user.id)
              # 計算邏輯...

      通過第二段代碼可以看到更容易實現依賴注入,通常實際使用中還結合依賴注入容器(IoC)自動化依賴的創建和注入,但這超出本篇的篇幅了。

       

       

      更容易暴露設計問題

      構造函數僅做賦值操作,還能更容易得暴露類的設計問題。當構造函數變得臃腫或復雜時,這通常表明存在更深層次的設計缺陷。

      比如一個類的構造函數有大量參數時,通常意味著類承擔過多的職責,比如:

      # 需要引起警覺:參數過多的構造函數
      class UserReport:
          def __init__(self, user, activity_list, login_calculator, active_days_calculator, 
                      visualization_tool, report_exporter, notification_system):
              self.user = user
              self.activity_list = activity_list
              self.login_calculator = login_calculator  
              self.active_days_calculator = active_days_calculator
              self.visualization_tool = visualization_tool
              self.report_exporter = report_exporter
              self.notification_system = notification_system
              self.statistics = {}

       

      一個常見的解決思路是使用Builder模式,讓初始化過程更加優雅,但這通常只能掩蓋問題,而不是解決問題

      因此可以將過多參數的構造函數當做red flag,正確的解決辦法是重新查看類的設計,進行職責分離:

      # 核心報告類,只關注數據和基本統計
      class UserReport:
          def __init__(self, user, activities):
              self.user = user
              self.activities = activities
              self.statistics = {}
          
          def calculate(self, calculator):
              self.statistics = calculator.compute(self.activities)
              return self
      
      # 分離的統計計算
      class ActivityStatistics:
          def compute(self, activities):
              login_count = len([a for a in activities if a.action == 'login'])
              unique_days = len(set(a.date for a in activities))
              return {"logins": login_count, "active_days": unique_days}
      
      # 分離的報告導出功能
      class ReportExport:
          def to_pdf(self, report):
              # PDF導出邏輯
              pass
          
          def to_excel(self, report):
              # Excel導出邏輯
              pass
      
      # 分離的通知功能
      class ReportNotification:
          def send(self, report, recipients):
              # 發送通知邏輯
              pass

      那么類的調用就會變得非常清晰:

      # 清晰的職責分離
      user = User(42, "John Doe", "john@example.com")
      activities = activity_database.get_user_activities(user.id)
      
      # 創建和計算報告
      calculator = ActivityStatistics()
      report = UserReport(user, activities).calculate(calculator)
      
      # 導出報告(如果需要)
      if export_needed:
          exporter = ReportExport()
          pdf_file = exporter.to_pdf(report)
      
      # 發送通知(如果需要)
      if notify_admin:
          notifier = ReportNotification()
          notifier.send(report, ["admin@example.com"])

      這種方式每個類都有明確的單一職責,構造函數簡單明了,同時功能可以按需組合使用以及測試變得簡單(可以單獨測試每個組件)。

       

      特例

      某些情況下,構造函數除了賦值,還可以做一些其他工作也是合理的,如下:

      參數合法性檢查

      在構造函數中進行基本的參數驗證是合理的,這確保對象從創建之初就處于有效狀態,例如下面例子,只要構造函數不進行外部依賴操作或復雜的邏輯運算都是合理的

      class User:
          def __init__(self, id, name, email):
              # 基本參數驗證
              if id <= 0:
                  raise ValueError("User ID must be positive")
              if not name or not name.strip():
                  raise ValueError("User name cannot be empty")
              if not email or "@" not in email:
                  raise ValueError("Invalid email format")
              
              self.id = id
              self.name = name
              self.email = email

       

      簡單的派生值計算

      有時,在構造函數中計算一些簡單的派生值是合理的,只要在整個類聲明周期,計算后的值都不變:

      class Rectangle:
          def __init__(self, width, height):
              if width <= 0 or height <= 0:
                  raise ValueError("Dimensions must be positive")
              
              self.width = width
              self.height = height
              # 簡單的派生值計算
              self.area = width * height
              self.perimeter = 2 * (width + height)

       

      不可變對象的初始化

      對于不可變對象(創建后狀態不能改變的對象),構造函數需要完成所有必要的初始化工作:

      class ImmutablePoint:
          def __init__(self, x, y):
              self._x = x
              self._y = y
              # 預計算常用值
              self._distance_from_origin = (x**2 + y**2)**0.5
          
          @property
          def x(self):
              return self._x
          
          @property
          def y(self):
              return self._y
          
          @property
          def distance_from_origin(self):
              return self._distance_from_origin

       

       

      小結

      一個設計合理的構造函數,是打造易維護、易測試、易擴展系統的基礎。我們應始終堅持構造函數「僅做賦值和必要的基礎驗證」這一原則,使代碼更為清晰和靈活。

      簡單的構造函數能帶來以下優勢:

      • 易于維護:職責單一、副作用少,便于后續的調試與迭代。
      • 易于測試:不依賴外部環境,能輕松實現模擬和單元測試。
      • 架構更清晰:便于實現依賴注入,更符合SOLID原則,也能更快地識別設計上的問題。

      當我們發現構造函數開始復雜化,參數越來越多時,這通常是代碼設計本身出現了問題,而不是一個能用Builder模式等技巧快速掩蓋的問題。正確的做法是退一步重新審視類的職責,及時進行重構。

      當然,在實際編碼過程中,有時候我們可能會做出一定程度的妥協,例如對參數進行基本合法性檢查、簡單的數據派生計算,或者初始化不可變對象。這些情況應該是少數的例外,而不是普遍的規則。

      總之,通過保持構造函數的簡潔和直觀,我們不僅能夠寫出高質量的代碼,更能及早發現和解決潛在的設計問題,使整個系統更加穩固和易于維護。

       

      posted @ 2025-03-31 08:56  CareySon  閱讀(1588)  評論(4)    收藏  舉報
      主站蜘蛛池模板: 成年女人免费碰碰视频| 日本久久99成人网站| 国产一区二区三区亚洲精品| 久久精品国产亚洲AⅤ无码| 亚洲av永久无码精品漫画| 成人精品区| 国产成人高清精品亚洲| 艳妇臀荡乳欲伦交换h在线观看| 亚洲熟妇av一区二区三区宅男| 长子县| 日韩欧激情一区二区三区| 亚洲二区中文字幕在线| 灵川县| 久久99精品久久久久久青青| 人妻精品动漫H无码中字| 国产成人精品1024免费下载| 国产精品任我爽爆在线播放6080| 日本九州不卡久久精品一区| 欧美日韩一线| 人妻少妇88久久中文字幕| 泽库县| 久久精品国产福利一区二区| 人妻无码中文字幕| 麻豆人妻| 国产极品尤物粉嫩在线观看| 欧美成人www免费全部网站| 天堂av成人网在线观看| 精品超清无码视频在线观看| 国产永久免费高清在线| 亚洲中文字幕伊人久久无码 | 亚洲国产美女精品久久久| 国内精品久久久久影院日本| 亚洲成人动漫在线| 丝袜a∨在线一区二区三区不卡| 在线国产你懂的| 国产精品国产精品偷麻豆| 十八禁国产一区二区三区| 久久久www免费人成精品| 国产极品尤物粉嫩在线观看| 国产精品色内内在线播放| 成人免费无遮挡在线播放|