iT邦幫忙

0

深入探討--「functional programming」或「臭長程式碼」或「.......」?

  • 分享至 

  • xImage

圖片模糊拍謝

各位大概看一下結構就好了

https://ithelp.ithome.com.tw/upload/images/20230118/20134378m93MxVhj04.png

以上是網路的教學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)
這樣真的會比原本那些流程判斷的寫法清楚乾淨嗎?
怎麼覺得只是變臭長了?

閱讀程式
上面文章提供很多種解法,似乎沒探討到這種架構...???
以上長篇,還請益高手說明~~感恩

看更多先前的討論...收起先前的討論...
powerc iT邦研究生 5 級 ‧ 2023-01-19 09:34:46 檢舉
回覆你的更新: 樓下大大有提到單一功能原則,從這來看,你的Division_Four、Division_One_Hundren、Division_four_Hundren的內容根本不用拆,就寫在一個方法就好,原因是這三個並不會獨立使用,因為是你要拿來算"閏年"的,基本上這個定義不會改變
froce iT邦大師 1 級 ‧ 2023-01-19 10:17:16 檢舉
同意樓上,基本上沒重用或不是太複雜就不用拆。

FP 最重要的精神是盡量避免副作用和宣告式表意。
我個人認為盡量避免副作用這點是最重要的重點,而且這在各種寫作方式都該注意。該注意的重點是這裡,拆函式只是細節而已。
尤其是client端 JS 是很容易汙染全域變數的,加上又是動態弱型別語言,基本上錯了你都不容易察覺。
@powerc 我也覺得沒必要耶~說真的~

會這樣寫是deh前輩所說:「像他那樣拆開,假設外部是呼叫最後那個function
該function做什麼很清楚啊,裡面使用的function name都明白的告訴你了」
所以我才舉這個例子做說明的,目的就是要確認意涵。

畢竟認真要看我原本題目裡面的函式內容,也都「不會獨立使用」,
因為是要拿來驗證表單密碼的,基本上定義不會改變。
所以我才覺得deh前輩沒有直指核心問題,因此延伸出來更新的部分~~
@froce 您提出的污染讓我驚醒了一部份,學習了。

主要是針對deh前輩說,不拆的話:「寫在一起一個function裡面塞一堆東西只會更讓人看不懂」覺得心生疑問..........
人家不就只有一行(笑
froce iT邦大師 1 級 ‧ 2023-01-19 10:45:06 檢舉
> 主要是針對deh前輩說,不拆的話:「寫在一起一個function裡面塞一堆東西只會更讓人看不懂」覺得心生疑問..........

人家說的也沒錯,你今天舉的例子是算閏年,但真實情況遇到的很可能不會那麼簡單,譬如是檢查權限、打折邏輯等,那你就會遇到明明沒幾行也不會重用,但你還是會希望拆出來的狀況。
拆函式是要看經驗看狀況的。
@froce 了解,沒有啦我舉例不是亂舉。
我是針對我問題中「影片裡」的code就是這麼單純簡單呀...
這也是我產生疑惑的主要原因哦~~
謝謝說明
froce iT邦大師 1 級 ‧ 2023-01-19 10:57:29 檢舉
我要教人當然不會拿複雜的例子教啊...我拿複雜的例子寫教學基本上就是不想教,除非真的就是這麼複雜。科科
我則是不太想寫程式教學。最多就是COPY原版的程式做一下說明。
而太過複雜的,更不會想寫。因為給初學者看也只是看不懂。
deh iT邦研究生 1 級 ‧ 2023-01-19 23:23:27 檢舉
例子的部分我覺得拆成那樣也沒什麼不好,假設對方不知道什麼是閏年,就可以很簡單的看出你在做什麼,就是自己寫的時候比較累。如同上面各位的回答,在更複雜的判斷時好處才會明顯。而教學內容的目的應該是想讓你清楚每一步在幹嘛,我覺得那樣做也比都寫在同一個function然後用註解說明還清晰易懂。
@deh 謝謝前輩解惑 了解了 真是一門學問
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
fillano
iT邦超人 1 級 ‧ 2023-01-19 11:00:35
最佳解答

上面大大都回答很好,我就根據最近的經驗補充一下。

最近有一個需求是針對一些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;
    .....
  });
});

寫成這樣當然無法任意組和。要能任意組和的話,需要:

  1. 把每個request包裝進一個函數,狀態變化跟檢查項目透過參數傳給他
  2. request body透過一個函數來產生,狀態變化就當作他的參數,這樣就可以控制request的條件
  3. 不可能每次都一行一行寫expect,所以把需要的chai進一步包裝成方法(equal, is.a etc.)的規則,利用json query查詢要檢查的項目跟預期值來組合成規則,這樣就可以根據需求產生規則,丟進request函數讓他在request後做檢查
  4. 為了更方便寫規則,再包裝一下寫一個規則產生器

細節我就不多說,最後變成這樣:

....
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函數組合起來,丟給他不同的參數,就可以快速組合出複雜的測試。

實際測試畫面像這樣:
https://ithelp.ithome.com.tw/upload/images/20230119/20000108trQpPfn391.png

這裡面會用到前面幾位大大提的的觀念,只是給你一個實做的例子。雖然很多功能還沒實現,不過已經可以根據QA回報的問題重現bug...

好酷喔 謝謝分享欸  很讚

選您為最佳解答 但其實各位的回答都非常好 感謝各位~

1
deh
iT邦研究生 1 級 ‧ 2023-01-18 22:44:22

姑且不論functional programming
物件導向裡面也有SOLID
其中的Single responsibility principle也會告訴你不該把一堆功能放在同一個class內

回到你的問題
寫在一起一個function裡面塞一堆東西只會更讓人看不懂
你function裡面有100行,別人要看100行才知道你在幹嘛

像他那樣拆開,假設外部是呼叫最後那個function
該function做什麼很清楚啊,裡面使用的function name都明白的告訴你了
細節的東西有需要再進去看就好了
你function好好拆開,別人可能看5行就知道你在幹麻了

關鍵字:SOLID、Clean Code

您好 謝謝您提供資料給我研究 已經更新好我的疑問~
加入在文章之中 再麻煩您幫我確認是否我有誤解
謝謝指教~~~

3
ckp6250
iT邦好手 1 級 ‧ 2023-01-19 05:58:19

舉一個例子,底下是我的一段 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 比較利人利己,當我退休後,接手的人才好辦事。

deh iT邦研究生 1 級 ‧ 2023-01-19 23:08:53 檢舉

真看過這種東西= =

2

這是一種零件式的寫法,不能說它好也不能說它壞。

如果說要用比較白話的說法。
就是step化的一種觀念。

這也是早期出現的物件模式的寫法。
這種寫法有個好處。

那些零件化的函式。你可以不需要去理會它是在做啥。且幾乎不用再去修改它。
當然這還得看寫的人就是了,並不是絕對。

你只要知道如何組合它就行了。

當然了,我一開始就有說,它並不能說好,也不能說壞。
因為好處在於比較可以靈活應用。缺點就是得先了解各函式的用處。

所以這得看情況來決定。

不過現今新的寫法,已經也將其模組化處理了。
也就是說你所看到的function。大多也化成了物件的方法之一。
這樣會更容易處理。這種寫法也可以算是模組化的前身。

很好的比喻,我喜歡這個說法!

1
whitefloor
iT邦研究生 2 級 ‧ 2023-01-19 15:16:58

這得看實際用例

舉例在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)
    ...
}

謝謝分享

我要發表回答

立即登入回答