圖片模糊拍謝
各位大概看一下結構就好了
以上是網路的教學code 我發現不只這段 其他頻道主也都很喜歡類似寫法
也就是先一堆的
function A()
function B()
function C()最後用
function D()去處理前面的一堆function 這樣子的架構
最近剛好閱讀到functional programming 覺得有點深奧難懂
想請問這樣子的例子算不算是呢??
如果不是 那這樣是否有某種名稱或是屬於什麼習慣??
還有到底為什麼要這樣搞,感覺拆成這樣比較凌亂(?
至少對我來說 不需要額外開一個函式來取一個長長一串function去call他....
因為本人對這方面比較不熟
所以希望各位前輩能來說明一些例子舉例(或者跟OO對比讓我比較好懂
讓我了解有哪些方法式乾淨的code
謝謝各位高手~~~
更新:
先謝謝並回應一樓的解答 我是考慮到函式裡面才一行判斷
真的有必要?(只是一個判斷式 並沒有牽涉太多複雜的邏輯運算)
這邊補充給各位一個例子 拿平年閏年的運算
來驗證我發問的那種寫code風格 是否太過「囉嗦」還是其實是很清楚、很棒的架構?
用函式包裝寫成下面:
var f=function(year){
function Division_Four(year){
return year % 4 == 0;
}
function Division_One_Hundren(year){
return year % 100 != 0;
}
function Division_four_Hundren(year){
return year % 400 == 0;
}
function Say_Ans(booleen){
if (booleen){
console.log('閏年');
}else{
console.log('平年');
}
}
function Operate_Three_Condition(year){
return Division_Four(year)&&Division_One_Hundren(year)||Division_four_Hundren(year);
}
Say_Ans(Operate_Three_Condition(year));}(2020)
如果照您說的,那似乎這樣用四個函式去封裝,也不是糟糕的code?
(我甚至把它封裝起來XDDDDD)
這樣真的會比原本那些流程判斷的寫法清楚乾淨嗎?
怎麼覺得只是變臭長了?
閱讀程式
上面文章提供很多種解法,似乎沒探討到這種架構...???
以上長篇,還請益高手說明~~感恩
上面大大都回答很好,我就根據最近的經驗補充一下。
最近有一個需求是針對一些Legacy API寫自動測試,關鍵是正常流程是要打十幾個API才能跑完流程,所以需要可以隨時把測試組合起來的彈性,這樣需要把打每個API可能的狀態變化跟要檢查的項目當作參數,把每個request包裝在函式裡面,才方便組合。
我們用mocha / chai / supertest來做這一類的測試,通常的寫法:
const config = require('config');
const request = require('supertest');
const v1api = request(config.domain);
const { expect } = require('chai');
describe('integration test', () => {
const fixture = {};
it('normal happy case', async () => {
const response = await v1api
.post(config.api001path)
.set({auth: `Bearer: ${config.v1apiToken}`})
.send(config.api001body);
expect(response.status).to.equal(200);
const body = JSON.parse(response.text);
expect(body.result).to.equal('success');
expect(body.data.reserved_id).to.be.a(Number);
fixture.reserved_id = body.data.reserved_id;
.....
});
});
寫成這樣當然無法任意組和。要能任意組和的話,需要:
細節我就不多說,最後變成這樣:
....
const {
....
api001Request,
....
} = require('./services/apiRequests);
const {
....
RuleCreator,
....
} = require('./services/testHelpers);
....
describe('integration test', () => {
const fixture = {};
it('normal happy case', async () => {
const rule1 = new RuleCreator()
.db('reservation', 'lengthOf', '', 1)
.body('lengthOf', 'data.create', 1)
.body('lengthOf', 'data.match', 0)
.create();
await api001Request(fixture, 'driver1', false, rule1);
....
});
});
這樣只要把每個不同的api request函數組合起來,丟給他不同的參數,就可以快速組合出複雜的測試。
實際測試畫面像這樣:
這裡面會用到前面幾位大大提的的觀念,只是給你一個實做的例子。雖然很多功能還沒實現,不過已經可以根據QA回報的問題重現bug...
姑且不論functional programming
物件導向裡面也有SOLID
其中的Single responsibility principle也會告訴你不該把一堆功能放在同一個class內
回到你的問題
寫在一起一個function裡面塞一堆東西只會更讓人看不懂
你function裡面有100行,別人要看100行才知道你在幹嘛
像他那樣拆開,假設外部是呼叫最後那個function
該function做什麼很清楚啊,裡面使用的function name都明白的告訴你了
細節的東西有需要再進去看就好了
你function好好拆開,別人可能看5行就知道你在幹麻了
關鍵字:SOLID、Clean Code
舉一個例子,底下是我的一段 select 指令
SELECT
d.借方科目 AS 借方代碼,
e.cname AS 借方科目,
d.借方金額,
d.貸方科目 AS 貸方代碼,
f.cname AS 貸方科目,
d.貸方金額
FROM
(
SELECT
max( c.借方科目 ) AS 借方科目,
sum( c.借方金額 ) AS 借方金額,
max( c.貸方科目 ) AS 貸方科目,
sum(- c.貸方金額 ) AS 貸方金額
FROM
(
SELECT
func_數值累加 ( 'i', 1 ) AS RecNo,
b.subjectid AS 借方科目,
b.summoney AS 借方金額,
' ' AS 貸方科目,
0.As 貸方金額
FROM
(
SELECT
a.subjectid,
sum( a.lastmoney ) AS summoney
FROM
(
SELECT LEFT
( subjectid, @vLen ) AS subjectid,
beginmoney *
IF
( dc = 1, 1.,- 1.)* @faciend AS lastmoney
FROM
accbegin
WHERE
YEAR BETWEEN @Byear
AND @Lyear UNION ALL
SELECT LEFT
( subjectid, @vLen ) AS subjectid,
subamount *
IF
( dc = 1, 1.,- 1.) lastmoney
FROM
accvoucher
WHERE
accvoucher.date BETWEEN Bdate
AND LDate
) a
GROUP BY
1
) b
WHERE
b.summoney > 0 UNION ALL
SELECT
func_數值累加 ( 'j', 1 ) AS RecNo,
' ' AS 借方科目,
0.As 借方金額,
b.subjectid AS 貸方科目,
b.summoney AS 貸方金額
FROM
(
SELECT
a.subjectid,
sum( a.lastmoney ) AS summoney
FROM
(
SELECT LEFT
( subjectid, @vLen ) AS subjectid,
beginmoney *
IF
( dc = 1, 1.,- 1.)* @faciend AS lastmoney
FROM
accbegin
WHERE
YEAR BETWEEN @Byear
AND @Lyear UNION ALL
SELECT LEFT
( subjectid, @vLen ) AS subjectid,
subamount *
IF
( dc = 1, 1.,- 1.) lastmoney
FROM
accvoucher
WHERE
accvoucher.date BETWEEN Bdate
AND LDate
) a
GROUP BY
1
) b
WHERE
b.summoney < 0
) c
GROUP BY
c.RecNo
) d
LEFT OUTER JOIN accsubject e ON rpad( d.借方科目, 7, '0' ) = e.subjectid
LEFT OUTER JOIN accsubject f ON rpad( d.貸方科目, 7, '0' ) = f.subjectid;
它其實包含了很多細節,就好像一個個的 function , 如果分拆成很多道 select , 別人(包括日後的我自己)就能知道我在幹嘛,但當我把它摻摻做一夥,三個月後,連我自己都不知道我在幹嘛,如果有一天客戶要求變個條件或格式,我都不知從何改起。
以前,我以【一行 select 包含 10 道 select】為傲,但我現在覺得,10 道 select 比較利人利己,當我退休後,接手的人才好辦事。
這是一種零件式的寫法,不能說它好也不能說它壞。
如果說要用比較白話的說法。
就是step化的一種觀念。
這也是早期出現的物件模式的寫法。
這種寫法有個好處。
那些零件化的函式。你可以不需要去理會它是在做啥。且幾乎不用再去修改它。
當然這還得看寫的人就是了,並不是絕對。
你只要知道如何組合它就行了。
當然了,我一開始就有說,它並不能說好,也不能說壞。
因為好處在於比較可以靈活應用。缺點就是得先了解各函式的用處。
所以這得看情況來決定。
不過現今新的寫法,已經也將其模組化處理了。
也就是說你所看到的function。大多也化成了物件的方法之一。
這樣會更容易處理。這種寫法也可以算是模組化的前身。
這得看實際用例
舉例在Go裡面有些unexport struct or variable
用FP處理就很好用,因為你不希望被隨便更改
最明顯的時候是你有unexport struct,然後struct有配interface把field回傳
var param int
func AddParam(i int){
param += i
}
func MinusParam(i int){
param -= i
}
那如果不想要寫成FP,通常就會改用compose的方法,通常是做在API middleware
好處就是好懂,又好改,但是整頁程式碼很長,不過通常會有IDE跟要專注修改的片段,就沒問題
func FirstAPIMiddleWare(ctx *gin.context){
input := struct{}
ctx.ShouldBindJSON(input)
...
}
func SecondAPIMiddleWare(ctx *gin.context){
input := struct{}
ctx.ShouldBindJSON(input)
...
}