Julia 代碼樣式

2018-08-12 21:26 更新

代碼樣式

以下各節(jié)從幾方面介紹了符合語言習慣的 Julia 編碼風格。這些規(guī)則都不是絕對的;它們僅僅是幫您熟悉這門語言,或是幫您可以在許多可替代性設計中能夠做出選擇的一些建議而已。

寫成函數(shù),別寫成腳本

編寫代碼作為在一系列步驟中最高級的辦法,是可以快速開始解決問題的,但您應該試著盡快把一個程序分成許多函數(shù)。函數(shù)具有更好的可重用性和可測試性,并可以更好闡明它們正在做什么,它們的輸入和輸出是什么。此外,由于 Julia 的編譯器工作原理,在函數(shù)中的代碼往往比最高級別的代碼運行得更快。

同樣值得強調(diào)的是,函數(shù)應該以參數(shù)來代替,而不是直接在全局變量(除了像 pi 那樣的常量)上操作。

避免類型過于嚴格

代碼應盡可能通用。相較于這樣的代碼書寫:

    convert(Complex{Float64}, x)

使用有效的泛型函數(shù)是更好的:

    complex(float(x))

第二種寫法把 x 轉(zhuǎn)換成一個適當?shù)念愋?,而不是一直用一個相同的類型。

這種類型特點是特別地與函數(shù)自變量相關。例如,不聲明一個參數(shù)是 Int 類型或 Int32 類型,如果在這種情況下還可以保持是任何整數(shù),那就應該是用 Integer 抽象表達出來的。事實上,在許多情況下您都可以把自變量類型給忽視掉,除非一些需要消除歧義的時候,由于如果一個類型不支持任何必要操作就會被忽略,那么一個 MethodError 不管怎樣也都會被忽略掉。(這被大家認為是 duck typing。)

例如,考慮以下 addone 函數(shù)中的定義,這個功能可以返回 1 加上它的自變量。

    addone(x::Int) = x + 1             # works only for Int
    addone(x::Integer) = x + one(x)    # any integer type
    addone(x::Number) = x + one(x)     # any numeric type
    addone(x) = x + one(x)             # any type supporting + and one

最后一個 addone 的定義解決了所有類型的有關自變量的 one 函數(shù)(像 x 類型一樣返回 1 值,可以避免不想要的類型提供)和 + 函數(shù)的問題。關鍵是要意識到,僅僅是定義通用的 addone(x) = x + one(x) 寫法也是沒有性能缺失的,因為 Julia 會根據(jù)需要自主編譯到專業(yè)的版本。舉個例子,您第一次調(diào)用 addone(12) 的時候, Julia 會自動為 x::Int 自變量編譯一個 addone 函數(shù),通過調(diào)用一個內(nèi)聯(lián)值 1 代替 one。因此,上表前三個定義全都是重復的。

在調(diào)用程序中解決額外的自變量多樣性問題

取代這種寫法:

    function foo(x, y)
        x = int(x); y = int(y)
        ...
    end
    foo(x, y)

利用以下的寫法更好:

    function foo(x::Int, y::Int)
        ...
    end
    foo(int(x), int(y))

第二種寫法更好的方式,因為 foo 并沒有真正接受所有類型的數(shù)據(jù);它真正需要的是 Int S。

這里的一個問題是,如果一個函數(shù)本質(zhì)上需要整數(shù),可能更好的方式是強制調(diào)用程序來決定怎樣轉(zhuǎn)換非整數(shù)(例如最低值或最高值)。另一個問題是,聲明更具體的類型會為未來的方法定義提供更多的“空間”。

如果函數(shù)修改了它的參數(shù),在函數(shù)名后加 !

取代這種寫法:

    function double{T<:Number}(a::AbstractArray{T})
        for i = 1:endof(a); a[i] *= 2; end
    a
    end

利用以下寫法更好:

    function double!{T<:Number}(a::AbstractArray{T})
        for i = 1:endof(a); a[i] *= 2; end
    a
    end

Julia 標準庫在整個過程中使用以上約定,并且 Julia 標準庫還包含一些函數(shù)復制和修飾形式的例子(例如 sortsort!),或是其它只是在修飾(例如 push!, pop!,splice!)的例子。這對一些也要為了方便而返回修改后數(shù)組的函數(shù)來說是很典型的。

避免奇葩的類型集合

Union(Function,String) 這樣的類型,說明你的設計有問題。

盡量避免空域

當使用 x::Union(Nothing,T) 時,想想把 x 轉(zhuǎn)換成 nothing 這個選項是否是必要的。以下是一些可供選擇的替代選項

  • 找到一個安全的默認值來和 x 一起初始化
  • 介紹另一種缺少 x 的類型
  • 如果有許多類似 x 的域,就把它們存儲在字典中
  • 確定當 xnoting 時是否有一個簡單的規(guī)則。例如,域通常是以 nothing 開始的,但是是在一些定義良好的點被初始化。在這種情況下,要首先考慮它可能沒被定義。

避免復雜的容器類型

通常情況下,像下面這樣創(chuàng)建數(shù)組是沒什么幫助的:

    a = Array(Union(Int,String,Tuple,Array), n)

在這種情況下 cell(n) 這樣寫更好一些。 這也有助于對編譯器進行注釋這一特定用途,而不是試圖將許多選擇打包成一種類型。

使用和 Julia base/ 相同的命名傳統(tǒng)

  • 模塊和類型名稱以大寫開頭, 并且使用駝峰形式: module SparseMatrix, immutable UnitRange.
  • 函數(shù)名稱使用小寫 (maximum, convert). 在容易讀懂的情況下把幾 個單詞連在一起寫 (isequal, haskey). 在必要的情況下, 使用下劃 線作為單詞的分隔符. 下劃線也可以用來表示多個概念的組合 (remotecall_fetch 相比 remotecall(fetch(...)) 是一種更有效的 實現(xiàn)), 或者是為了區(qū)分 (sum_kbn). 簡潔是提倡的, 但是要避免縮寫 (indexin 而不是 indxin) 因為很難記住某些單詞是否縮寫或者怎么 縮寫的.

如果一個函數(shù)需要多個單詞來描述, 想一下這個函數(shù)是否包含了多個概念, 這樣 的情況下最好分拆成多個部分.

不要濫用 try-catch

避免錯誤要比依賴找錯好多了。

不要把條件表達式用圓括號括起來

Julia 在 if 和 while 語句中不需要括號。所以要這樣寫:

    if a == b

來取代:

    if (a == b)

不要濫用 ...

剪接功能參數(shù)可以讓人很依賴。取代 [a..., b...] 這種寫法,簡單的 [a, b] 這樣寫就已經(jīng)連接數(shù)組了。collect(a) 的寫法要比 [a...] 好,但是因為 a 已經(jīng)是可迭代的了,直接用 a 而不要把它轉(zhuǎn)換到數(shù)組中也許會更好。

不要使用不必要的靜態(tài)參數(shù)

信號函數(shù):

    foo{T<:Real}(x::T) = ...

應該這樣寫:

    foo(x::Real) = ...

特別是如果 T 沒被用在函數(shù)主體。即使 T 被用在函數(shù)主體了,如果方便的話也可以被 typeof(x) 替代。這在表現(xiàn)上并沒有什么差異。要注意的是,這不是對一般的靜態(tài)參數(shù)都要謹慎,只是在它們不會被用到時要特別留心。

還要注意容器類型,特別是函數(shù)調(diào)用中可能需要的類型參數(shù)??梢缘?FAQ 如何聲明“抽象容器類型”的域 來查看更多信息。

避免對實例或類型判斷的困擾

一些如以下的定義是十分讓人困擾的:

    foo(::Type{MyType}) = ...
    foo(::MyType) = foo(MyType)

您要決定問題的概念是應被寫作 MyType 或是 MyType(),并要堅持下去。

最好的類型是用默認的實例,并且在解決某些問題需要方法時,再添加包括 Type{MyType} 的一些方法好一些。

如果一個類型是一個有效的枚舉,它就應該被定義為一個單一的(理想情況下不變的)類型,而枚舉變量是它的實例。構(gòu)造函數(shù)和一些轉(zhuǎn)換可以檢測值是否有效。這項設計最好把枚舉做成抽象類型,把“值”做成其子類型。

不要濫用 macros

您要注意什么時候一個 macros 可以真的代替函數(shù)。

在 macros 中調(diào)用 eval 實在是個危險的標志;這意味著 macros 只有在被最高級調(diào)用的時候才會工作。如果這樣一個 macros 被寫為一個函數(shù),它將自然地訪問它需要的運行時值。

不要在接口層暴露不安全的操作

如果您有一個使用本地指針的類型:

    type NativeType
        p::Ptr{Uint8}
        ...
    end

不要像下面這樣寫定義:

    getindex(x::NativeType, i) = unsafe_load(x.p, i)

問題是,這種類型的用戶可能在不知道該操作是不安全的情況下就寫 [i],這容易導致內(nèi)存錯誤。

這樣的函數(shù)應該能檢查操作,以確保它是安全的,或是在它的名字中有不安全的地方時可以提醒調(diào)用程序。

不要重載基容器類型的方法

像下面這樣書寫定義是有可能的:

    show(io::IO, v::Vector{MyType}) = ...

這樣寫將提供一個特定新元素類型的向量的自定義顯示。雖然很讓人想嘗試,但卻是應該避免的。麻煩的是,用戶會想用一個眾所周知的類型比如向量在一個特定的方式下的行為,也會過度定制它的行為,這都會使工作更困難。

注意類型的相等性

您一般要使用 isa<: (issubtype) 來測試類型而不會用 ==。在與已知的具體類型的類型進行比較時,要精確檢查類型的的相等性(例如 T == Float64),或者是您真的明白您究竟在干什么。

不要寫 x->f(x)

高階函數(shù)經(jīng)常被用作匿名函數(shù)來調(diào)用,雖然這樣很方便,但是盡量少這么寫。例如,盡量把 map(x->f(x), a) 寫成 map(f, a) 。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號