2009-04-12 72 views
6

當涉及到設計合適的執行報告時,我總是懷疑。代碼執行期間的報告信息:最佳設計

假設你有以下(愚蠢,簡單)的情況。我將使用python。

def doStuff(): 
    doStep1() 
    doStep2() 
    doStep3() 

現在,假設你想給的各個步驟的報告,如果出現錯誤等沒有真正的調試:應用程序的只是信息的行爲。

第一,簡單的辦法是把打印

def doStuff(): 
    print "starting doing stuff" 
    print "I am starting to do step 1" 
    doStep1() 
    print "I did step 1" 
    print "I am starting to do step 2" 
    doStep2() 
    print "I did step 2" 
    print "I am starting to do step 3" 
    doStep3() 
    print "I did step 3" 

在一般情況下,這是相當糟糕的。假設這段代碼將在一個庫中結束。我不希望我的圖書館打印出來。我希望它能夠默默地完成這項工作。不過,有時我想提供信息,不僅在調試情況下,而且還要向用戶通知實際正在進行的工作。打印也很糟糕,因爲您無法控制消息的處理。它只是stdout,除了重定向之外,你無能爲力。

另一種解決方案是有一個記錄模塊。

def doStuff(): 
    Logging.log("starting doing stuff") 
    Logging.log("I am starting to do step 1") 
    doStep1() 
    Logging.log("I did step 1") 
    Logging.log("I am starting to do step 2") 
    doStep2() 
    Logging.log("I did step 2") 
    Logging.log("I am starting to do step 3") 
    doStep3() 
    Logging.log("I did step 3") 

這樣做的好處是您可以爲您的日誌記錄服務知道一個獨特的位置,並且您可以根據需要調整此服務。您可以將其靜音,將其重定向到文件,標準輸出或甚至網絡。缺點是你與記錄模塊有很強的耦合。基本上你的代碼的每個部分都依賴於它,並且你有打電話到處登錄。

的第三種選擇是有一個清晰的界面報表對象,而且你通過它周圍

def doStuff(reporter=NullReporter()): 
    reporter.log("starting doing stuff") 
    reporter.log("I am starting to do step 1") 
    doStep1() 
    reporter.log("I did step 1") 
    reporter.log("I am starting to do step 2") 
    doStep2() 
    reporter.log("I did step 2") 
    reporter.log("I am starting to do step 3") 
    doStep3() 
    reporter.log("I did step 3") 

最後,你也可以通過記者對象doStepX(),如果他們有更多的話要說。 優點:它減少了與模塊的耦合,但它引入了與NullReporter對象實例化的耦合。 另:這可以通過使用無作爲默認和檢查呼叫日誌,這是笨拙的,因爲在Python中,你必須有條件每次(在C你可以定義一個宏)

def doStuff(reporter=None): 
    if reporter is not None: 
     reporter.log("starting doing stuff") 
     # etc... 

編輯寫之前得到解決選項是類似於Qt,並有一個emit()信號策略。隨着代碼的執行,它會發出帶有適當狀態代碼的信息,任何感興趣的人都可以訂閱這些信號並提供信息。不錯,乾淨,非常分離,但需要一些編碼,因爲我認爲這可以很快用包含的蟒蛇電池來完成。

最後,您可以用有意義的錯誤消息引發異常,但這當然只能在您退出錯誤條件時才能使用。它不適用於偶爾的報告。

編輯:我想澄清的事實,情況是更一般的,而不是僅限於一系列被調用的步驟。它也可能涉及控制結構:

if disconnected: 
    print "Trying to connect" 
    connect() 
else: 
    print "obtaining list of files from remote host" 
    getRemoteList() 

該報告還可以進入實際程序,所以你必須在connect()和getRemoteList()程序「打印」作爲第一條語句。因此

的問題是:

  • 你認爲什麼是一些代碼(特別是在圖書館的情況下)最好的設計是在同一時間沉默的時候噪音可能是顛覆性的客戶端,但是有用時冗長?
  • 如何處理邏輯代碼和報告代碼之間的平衡混合?
  • 代碼和錯誤檢查之間的混合已被解決,但有例外。可以做些什麼來劃分代碼邏輯報告的「噪音」?

編輯:爲心靈

更多的想法,我認爲這不僅是從邏輯代碼去耦日誌代碼的問題。我認爲這也是將信息生產和信息消費分開的問題。類似的技術已經存在,尤其是處理UI事件,但我並沒有真正看到應用於日誌記錄問題的相同模式。


編輯:我接受了來自馬塞洛的答案,因爲他在事實證據表明,妥協是在這種情況下,最好的解決辦法,而且也沒有銀彈指出。然而,所有其他人都是有意思的答案,我很高興能夠全部讚揚他們。謝謝您的幫助!

+0

我添加了一個賞金,看看是否可以在這個問題上做進一步的討論。此外,這是mu第一次賞金開放,所以我也很好奇。 – 2009-04-14 01:36:39

回答

1

我認爲有一點你必須畫一條線並作出妥協。 我看不出將日誌從系統中徹底解耦的原因,因爲您必須在某處以某種方式發送這些消息。

我會去默認的記錄模塊,因爲...這是默認模塊。它有很好的文檔記錄,並帶有默認庫,因此這裏不存在依賴關係問題。此外,你可以避免重蹈覆轍。

也就是說,如果你真的在做新的事情,你可以成爲全球記者的對象。您可以在流程開始時進行實例化和配置(記錄,不記錄,重定向流等,即使在每個流程/功能/步驟的基礎上),並從任何地方調用它,無需傳遞它(可能在多線程環境,但這將是最小的)。

你也可以把它放在另一個線程裏面並且抓住Qt的日誌事件。

4

我認爲圖書館的最佳解決方案是沿着添加圖書館的方向。

Log.Write(...) 

其中Log的行爲是從周圍環境(例如app.config或環境變量)中獲取的。 (我也認爲這是一個已經接近並解決了很多次的問題,雖然在設計領域有一些「甜蜜點」,但是對於你描述的情況,上面的答案是最好的IMO)。

我沒有看到任何好的方法來將'正常'部分的代碼從'日誌'部分'解耦'。記錄往往是相對非侵入性的;我偶爾發現Log.Write(...)是對現實世界代碼的分心。

+0

好吧,雖然沒有侵入性,但我認爲這不是代碼本身的解耦問題,而是關於向外部實體提供關於進度信息的概念。 – 2009-04-13 18:40:14

2

另一種選擇是在不記錄日誌的情況下編寫代碼,然後在執行代碼之前應用一些轉換來插入適當的日誌語句。這樣做的實際技術高度依賴於語言,但與編寫調試器的過程非常相似。

這可能是不值得增加的複雜性,雖然...

+0

AOP中的一種「編織者」。但o發現很難真正實現類似的東西。它面臨着很多實際的障礙。 – 2009-04-12 09:49:41

+0

實現這一點的簡單方法是暫時將內聯註釋更改爲函數調用。 – 2009-04-14 17:17:50

1

應該有工具,允許樣板日誌消息一拉「進帶參數的方法A(1,2,3)」,「從回國方法B的值爲X,花費10 ms「自動(並有選擇地)生成(在運行或部署時進行控制)。用手寫這些東西太無聊/重複/容易出錯。

不知道是否有,但。

如果您要編寫手動日誌消息,請確保包含一些有用的上下文信息(用戶ID,正在查看的URL,搜索查詢等),以便如果出現問題,您可以獲得更多信息不僅僅是方法名稱。

+0

我認爲所需要的是一種類似異常的系統,其中異常不用於控制執行,只是爲了報告事件已經發生。 – 2009-04-12 09:51:16

2

我發現this搜索Python的面向方面編程。我同意其他海報,不應將這種擔憂與核心邏輯混爲一談。

從本質上講,您想要記錄日誌的點可能不總是任意的,也可能是諸如「錯誤前的所有點」可能被切入點識別的概念。其他完全的任意點可以使用簡單的日誌記錄技術來捕獲。

+0

AO for python?我對去年閱讀的AO想法很感興趣,但是我認爲我不得不鑽研C#,Java或其他的東西......哇,那是從2003年開始的?涼。 – DarenW 2009-04-12 01:58:23

+0

我對AOP不太瞭解,但是使用AOP可以幫助您攔截常見的切入點,但並不能解決在代碼中放置任意點的問題,您希望爲即將發生的錯誤情況提供有意義的消息發生或信息狀態。我對嗎 ? – 2009-04-12 10:01:30

1

我會使用標準的logging模塊,它自Python 2.3以後就成爲標準庫的一部分。

這樣,很有可能人們看着你的代碼已經知道logging模塊是如何工作的。如果他們必須學習,那麼至少它有很好的文檔記錄,並且他們的知識可以轉移到也使用logging的其他庫。

是否有任何功能需要,但在標準logging模塊中找不到?

1

我認爲最簡單的解決方案是最好的。這取決於語言,但只使用一個非常短的,全球可訪問的標識符 - 在PHP中,我使用自定義函數trace($msg) - 然後只是實現並重新實現該代碼,因爲您認爲適合特定的項目或階段。

這是一個自動的在編譯版本,它是標準的調試器。如果你想看到有意義的標籤,你需要自己編寫這些標籤,不幸的是:)

或者你可以嘗試暫時將內嵌評論轉換爲函數調用,但我不確定這是行不通的。

1

作爲對您對信息生產/消費的編輯的迴應:對於一般情況來說,這是一個有效的關注點,但記錄不是一般情況。尤其是,您不應該依賴來自程序某個部分的日誌輸出來影響另一部分的執行。這確實將消費者與生產者的實施緊密結合在一起。

日誌記錄應該被認爲是附屬於您的主要執行。您的系統不應該知道或關心這些日誌文件的存在或內容(可能有監視工具除外)。既然如此,將原木與其生產的「消耗」分離的概念是無關緊要的。由於您沒有使用日誌來做任何有意義的事情,所以耦合不是問題。

2

我經常爲此使用DTrace。在OS X上,python和ruby都已經設置了DTrace鉤子。在其他平臺上,你可能必須自己做。但是能夠將調試跟蹤附加到正在運行的進程上,太棒了。如上所述,對於庫代碼(假設您正在編寫一個http客戶端庫),最好的選擇是傳入一個可選的記錄器作爲參數。 DTrace適用於在生產環境中(有時在其他地方)添加日誌記錄功能,但如果其他人可能想要訪問日誌以調試隨後調用您的代碼的日誌,那麼可選日誌記錄器作爲參數絕對是一種方式去。