小程序中的大道理--綜述

前言

以下將用一個小程序來探討一些大道理, 這些大道理包括可擴展性, 抽象與封裝, 可維護性, 健壯性, 團隊合作, 工具的利用, 可測試性, 自頂向下, 分而治之, 分層, 可讀性, 模塊化, 松耦合, MVC, 領域模型, 甚至對稱性, 香農的信息論等等.

為什么不用大程序來說大道理呢?

因為大程序太大了, 代碼一端上來, 讀者就暈菜了, 看不過來甚至壓根不想去看, 這樣說理就很抽象了, 效果反而不好.

小程序中也能說出大道理來嗎?

我們有句話, 叫"以小見大", 我們又常常有種說法, 叫:

麻雀雖小, 五毒俱全. (咦? 好像應該是五臟俱全…總之你明白我的意思就好了. )

所以呢, 小程序也是可以來說大道理的, 而且小程序又有短小的特點, 大家看得也沒那么累, 也很快能看懂. 畢竟那種代碼, 叫什么來著, “意大利面條式的代碼”, 大家在實際的開發中, 已經見得太多了.

意大利面(spaghetti), 翻譯過來好像叫"通心粉", 非常長的一條條, 彼此纏纏繞繞的, 所以"意大利面條式的代碼"就是又長又繞, 讓人非常頭痛的那種代碼.

按我們的習慣, 也許叫它"裹腳布式的代碼"大家覺得更熟悉, 更形象一點, 也正好符合"又長又臭"的特點.

啊, 說"又長又臭"可能有點刻薄了, 畢竟大家都可能寫過這樣的代碼(本人就寫過好多), 即便現在不會再寫這樣的代碼, 想當年應該也是寫過的, 除非你從一開始覺悟通天, 那我就無話可說了.

這種代碼我們在工作中見得太多了, 所以這里就不再弄出來考驗大家的毅力了, 閑話少提, 讓我們看個簡單的例子.

我們的例子

就是要打印出如下的一個三角形圖案:

  ****
*****

當然了, 這只是以三行為一個示例, 我們的程序應該接受任意的正整數, 比如, 給一個 5, 就要能打出 5 行的類似的三角形來. 讓我們來看看如何寫出這樣一個程序, 并在這個過程中借此兜售我們的大道理.

玩具式的代碼

我知道很多"數學帝"可能一眼就被圖案中的規律吸引過去了, 他們很快就指出星號是等差數列, 然后很快就弄出了計算每行前面要縮進多少個空格的公式, 然后呢, 一層循環, 二層循環, blablablah…然后最里面幾條優雅而性感的 print 語句, 搞掂!一種智商上的優越感油然而生, 接著他們可能就要問:

這么簡單的東西, 你也好意思拿出來講?

下面是這樣的一個代碼, 能夠完全實現以上要求(只演示了 3 行的情況):

public static void main(String[] args) {int i = 3;for (int j = 0; j < i; j++) {for (int k = 0; k < i - j; k++) {System.out.print(" ");}for (int z = 0; z < 2*j+1; z++) {System.out.print("*");}System.out.println();}
}

這里用的是 java 語言來演示, 包括以下的. 我相信像 java 這樣爛大街的語言, 即使你沒這個背景看懂也不是難事, 在代碼中也不會用到什么高深的特性. (這一點皆因我的能力有限所導致, 而不是想裝逼的意愿所能決定的~)

怎么說呢, 我們不要以上那種"玩具式"的代碼(toy program), 我們要的是生產級(production, 生產環境)的代碼.

生產級的代碼

讓我們來看看如何寫出這樣的代碼.

可擴展性(Extensibility)

首先呢, print 語句是絕對要避免的. 你要明白, print 語句寫得太死, 而需求是不斷在變化的, 有句話是怎么說的?

唯一不變的就是變化本身.

客戶今天跟你說的是要 print 這個圖案, 你要是按著客戶怎么說, 你就怎么做, 你可就慘了.

客戶哪一天突然又會說, 再加點特性, 要能輸出到文件;哪一天又說, 再加點 web service, 能供其它程序調用.

讓我們多留點心眼, 代碼如下:

public void printPattern(int lineCount) {String pattern = getPattern(lineCount);System.out.print(pattern);
}

我們先借助 getPattern 方法拿到要打印的內容, 這樣, 如果哪天要輸出到文件, 哪天要供 web service 調用, 我們都可以把這個 getPattern 方法提供出去.

我們只要多抽象出那么一層來, 就會給我們帶來很多方便.

抽象與封裝(Abstraction & Encapsulation)

抽象與封裝同時也是很多其它特性的基礎, 在后面我們還會不斷說到這一主題.

getPattern 就是一個抽象, 是對一系列動作的一個封裝.

可能有人會比較教條地認為抽象與封裝只能在類層次中進行, 這常常導致在類的內部缺少必要的抽象層次, 常常是一大件事情在一個方法里完成, 方法巨大巨長無比, 這樣的所謂面向對象編程不過是虛有其表, 其模塊性甚至還比不上那些用面向過程語言寫就的代碼.

printPattern 層面, 我們不需要知道 getPattern 的細節, 我們只需要傳入所需參數及定義好需要的返回值即可.

大道理: 定義好輸入與輸出, 描述清楚想要做的事, 先不用去管細節.

然后呢, 我們是不是需要手動去把這個方法寫出來呢?

利用好你的工具(Tools)

你不用手動去做這些, 以 eclipse 為例, 只要把光標定位到錯誤的地方(可以按Ctrl+“.”(點)快速定位), 然后按下"Ctrl+1", 然后選擇"Create Method"即可:

eclipse create method

工具將根據傳入參數及返回值自動為我們生成方法, 結果如下:

eclipse create method

只要輸入與輸出定義清楚了, 工具就能自動幫我們生成方法定義, 這里默認它是 private 的, 我們可以把它改成 public.

這里說的是 Eclipse 這個 IDE, 其它的我相信也會有類似的功能. 如果你偏好輕量級的文本編輯器, 那我就不敢說也一定有這些功能了.

利用好任務標識(Task Tags)

我們可以看到, 生成的代碼里有個 TODO, 顯示出了特別的顏色, 這是個任務標識.

類似的標識還有 FIXME, XXX, 甚至你還可以自定義標識.

打開 eclipse 的菜單-- windows–preferences, 在過濾框中輸入"task tag":

eclipse task tag

這些有什么用呢? 我們可以看下, 在編輯器的左右側, 都有顯著的標志提示有個任務標識存在;在 Markers 視圖里, 有列舉出這些標識:

eclipse todo task tag

在代碼質量分析工具 sonar 中, 它也會追蹤這些標識. 下圖是我在 sonar.oschina.net 上的一個項目的截圖:

sonar todo tag

這些有什么用呢? 我們在寫代碼中, 寫到一半, 很可能被某些難題卡住了, 為了不中斷正常的流程, 我們先用個 TODO 來標識, 然后就可以繼續地把一些簡單的問題先處理完, 再回過頭來對付這些.

又或者像現在這樣, 我們生成了出來了這個方法, 工具為我們自動加了入"TODO"的標識, 畢竟方法的主體還沒有, 可不巧的是, 現在到了下班時間了, 然后呢, 我們就可以存盤并提交到 svn 或者 git 上去了. 有人可能要說: "啊? 不是吧, 你的代碼都沒寫完你怎么就提交了? "

沒關系, 我們已經標識好了 TODO, 所以它會提醒我們還有工作是沒做完的. 另外我們為何如此著急提交呢? 因為我們并不是在單打獨斗:

團隊合作(Teamwork)

我們前面說了, 我們可能還要做輸出圖案到文件的需求, 很可能你有個同事哥們, 他就正做著這個模塊, 而他現在呢, 就在等著你這個 getPattern 方法. 你提交了, 他就可以繼續寫他的代碼了:

package org.jcc.core.demo;public class PatternFile {private Pattern pattern;public PatternFile(Pattern pattern) {this.pattern = pattern;}public void generatePatternFile(int lineCount) {String content = pattern.getPattern(lineCount);saveInFile(content);}private void saveInFile(String content) {// TODO Auto-generated method stub//System.out.println(content);}
}

可以看到, 他的類依賴你的類, 在他的方法 generatePatternFile 里還調用了 getPattern 方法, 你沒實現, 那又怎樣呢? 接口好了就行了!

面向接口編程(Interface)

有人可能比較死板, 比較教條主義, 以為呢, 說到接口就一定要弄個 interface, 其實呢, 我們這個方法 getPattern 就是一個承諾, 一個約定, 一個協議, 也是一個廣義上的接口.

有人可能要問, 你方法細節還沒有實現, 他怎么測試? 別擔心, 辦法會有的:

利用 Mockito 來測試

代碼如下:

@Test
public void testGeneratePatternFile() {// 用mockito來模擬接口的行為, 為此我們手動構建一個三行的圖案Pattern pattern = Mockito.mock(Pattern.class);String mockContent = "  *" + System.lineSeparator() + " ***" + System.lineSeparator() + "*****" + System.lineSeparator();// 當調用getPattern方法時, 就返回這里定義好的內容. Mockito.when(pattern.getPattern(3)).thenReturn(mockContent);// 測試generatePatternFile方法, 在它的里面將會調用getPattern方法PatternFile pf = new PatternFile(pattern);pf.generatePatternFile(3);// TODO 斷言文件存在并且文件中的內容與mockContent一致// assert that file is exists and content in file is equals the mock content
}

以上我們用一個 mock 對象以及 when, thenReturn 來主動模擬一個尚未實現的方法.

你也許對 Mock 之類的技術還不太了解, 但這些詞表達的意思我想大家都不難明白. Mock 的更詳細介紹請自行百度之.

借助 Mockito, 這個哥們就可以這樣寫好他的代碼, 并完成他的測試了, 然后可以提交他的代碼, 宣布工作完成, 接著他就可以飛到馬爾代夫去度假去了.

可以看到, 盡管我們的功能八字還沒一撇, 可只要我們堅持面向接口編程, 時時想著團隊合作, 經常提交已經寫好的代碼, 特別是公共接口方面的代碼, 我們的同事就能及時推進他們的工作, 甚至比我們還早完成, 這都是有可能的, 都是正常的, 也是我們應該追求的.

而利用好抽象及封裝, 我們還能得到好幾個好處:

可測試性(Testability)

通過以上舉例, 可以看到, 我們可以手動構建一個圖案, 并交給程序去判斷(注: 為了簡短起見, 代碼中省略了具體的 assert 細節). 而如果是開頭那樣直接就打印了呢? 你根本沒法讓程序去判斷, 只能通過人眼去觀察輸出, 這樣就給 自動化的測試(Automatic Test) 帶來了困難.

可重用性(Reusability)

getPattern 被抽象出來之后, 可以看到, 不但可以在 printPattern 方法里使用, 也可以在 generatePatternFile 方法里使用. 而如果按開頭那樣呢? 你沒法復用, 你還是不得不重構;又或者你可能只是簡單地把代碼復制一遍了, 再作些改動.

當然, 現在這個程序很小, 全部拷貝一遍好像也很快, 但如果是很大的程序呢? 又或者我們又要拓展到可供 web service 調用, 難道就這樣拷貝下去? 哪一天程序要做些小調整, 難道又要一一去修改嗎?

不要重復(DRY: Don’t Repeat Yourself)

管理重復性一直都是程序開發中的重大關切, 在目前這個小程序里, 這一問題還不是那么迫切, 這個在此就不作詳述, 以后會另寫一些文章來做些介紹.

好了, 說了一大通, 繞了一大圈, 測試也測了, 同事也度假去了, 我們也要趕緊我們的工作. 那么接下來是不是趕緊寫那些實現呢? 不!

我們已經介紹了不少的"ility"結尾的單詞, 接下來還要說到!我有點擔心大家說我"zhuangbility", 有句話說: “Don’t zhuangbility, zhuangbility leads to leipility”(莫裝逼, 裝逼遭雷劈), 沒辦法, 為了闡述這些大道理, 我也只好冒著被 leipility 的危險.

可維護性(Maintainability)

你首先把注釋寫好:

怎么說呢? 現在 IT 工作強度很大, 過勞死是不稀奇的事, 寫著寫著說不定哪天人就掛了. 一個人掛了不要緊, 工作可不能掛!(不是在說笑話, 貌似有些公司或老板表現出來的態度就是這樣的~)

別人要能順利接手你的活, 這是關鍵.

其實沒必要說"掛了"這些不吉利的話, 也可能是有人要生了, 比如你老婆要生了, 你也休產假去了, 你寫到一半, 老板把你的工作轉交給你的同事.

試想, 要是一點注釋都沒有, 你的同事接手起來就很困難, 他要加班加點才能早點弄清你的代碼的意圖. 所以呢, 不要害了你的同事!把代碼的可維護性做好, 大家的健康也才有更好的可維護性!

代碼如下:

/*** 獲取指定行數的圖案, 比如3行時: *   **  **** ******  * @param lineCount 指定的行數* @return 圖案的字符串表示, 包括換行符在內*/
public String getPattern(int lineCount) {// TODO Auto-generated method stubreturn null;
}

其實, 良好的命名同樣也是可維護性的關鍵, 比如上面的 getPattern, lineCount, 而不是像最前面那個示例中的 i, j, k, z 等亂七八糟的名字.

另外, 豐富的抽象層次也是如此, 這點我們后續還會不斷提及.

好了, 注釋也寫完了, 然后呢, 現在該輪到寫那個該死的等差數列了吧? 不!

健壯性(Robustness)

Robustness 又常常音譯成魯棒性.

作者在大學時讀的是自動化專業, 在那些自動控制理論里, 老出現什么魯棒性, 看了讓人犯暈, 不如直接叫健壯性.

我倒是想起了小時候老爸常買給我喝的 Robust(即樂百氏, 與娃哈哈類似的飲品), 味道是不錯, 不過喝完身體挺沒見得健壯到哪去, 也許喝得還不夠多~

我們先要把判斷做好, 輸入負數或者輸入的數字太大了, 你要拒絕它們, 同時在注釋中也作出說明:

/*** 獲取指定行數的圖案, 比如3行時: *   **  **** ******  * @param lineCount 指定的行數, 1-20之間* @return 圖案的字符串表示, 包括換行符在內*/
public String getPattern(int lineCount) {if (lineCount < 1) {throw new IllegalArgumentException("行數不能小于1!");}if (lineCount > 20) {throw new IllegalArgumentException("行數不能大于20!");}// TODO return null;
}

我知道我在這里說這些, 有些人可能已經不耐煩了, 他們想著的是寫那些有技巧的代碼, 那些有挑戰性的部分, 那些 tricky 的部分, 那些能體現出他們智商上的優越感的部分.

有個詞是怎么說的, “rocket science”(火箭科學, 喻指那些高精尖的技術), 特別的有些剛畢業的心氣很高的學生, 滿腦子想的可能就是這些. 可是呢, 類似情況不是沒有, 但通常是很少的:

騷年, 不是我在打擊你, 你也許真的想多了. 工作上, 我們多數時候處理的都是一些細節的問題, 一些瑣碎的事情, 一些按部就班的樣板式的代碼, 需要的不是多高的智商, 多么 tricky 的技巧, 要是是耐心, 細致, 嚴謹, 一絲不茍.

為何一開始就要把這些做好呢? 因為到了后面, 你就沒時間去做了. 這一點你一定要相信我, 以下引自 wiki 的"90-90法則":

前 90% 的代碼要花費你 90% 的開發時間, 剩余的 10% 的代碼要花費你另一個 90% 的開發時間.

The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.

–Tom Cargill, 貝爾實驗室

而最后如果因為時間緊急, 就這樣沒保護就上了生產環境, 一旦出了問題, 你會花更多的時間去收拾這些爛攤子, 而最終你還是不得不將這些補上.

有一個"墨菲定律"(Murphy’s Law)大意是這么說的:

有可能出錯的的東西一定會出錯.

現在不擦屁股, 后面還有得擦. 你省掉了紙尿褲, 你的程序就裸奔了, 你就等著洗更多的外套.

我們也常說: "該來的一定會來. "如果用電影<<無間道>>里的話來說呢, 那就是:

“出來混, 遲早要還的”. (哇塞, 說得太精彩了. 這些編導或者劇作家不去寫教科書太可惜了. )

所以呢, 不要有僥幸的心理, 把程序從一開始就寫健壯才是正道.

小結

說了半天, 我們甚至連一行核心代碼都沒寫, 不過, 文章至此倒是要先做一個階段了結了. 我們說寫代碼有個原則, 那就是方法不能太長, 最好一個屏幕就能顯示完, 否則看起來就很累了;自然的, 文章也不能寫得太長, 否則寫起來, 讀起來都很累人.

所以呢, 雖然一開始那里提了好多的道理, 本來也是想一扒到底的, 但扒到一半發現已經很長了, 所以上半身扒完, 就此腰斬, 下半身留待后面繼續扒, 下半身更精彩, 我們下回再見.

下一篇, 見 小程序中的大道理之二 .

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/166911.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166911.shtml
英文地址,請注明出處:http://en.pswp.cn/news/166911.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CMS指紋識別方式

一、手工識別 1.robots.txt文件 robots.txt文件我們寫過爬蟲的就知道,這個文件是告訴我們哪些目錄是禁止爬取的。但是大部分的時候我們都能通過robots.txt文件來判斷出cms的類型 如: 從wp路徑可以看出這個是WordPress的cms 這個就比較明顯了直接告訴我們是PageAdmin cms 也…

Python大語言模型實戰-記錄一次用ChatDev框架實現爬蟲任務的完整過程

1、模型選擇&#xff1a;GPT4 2、需求&#xff1a;在win10操作系統環境下&#xff0c;基于python3.10解釋器&#xff0c;爬取豆瓣電影Top250的相關信息&#xff0c;包括電影詳情鏈接&#xff0c;圖片鏈接&#xff0c;影片中文名&#xff0c;影片外國名&#xff0c;評分&#x…

C語言小練

給定兩個數&#xff0c;求這兩個數的最大公約數 本算法主要利用輾轉相除法求出兩個數的最大公約數。 int main(){int m0;int n0;int r0;scanf("%d %d",&m,&n);while(rm%n){mn;nr;} printf("%d\n",n);return 0; } 打印斐波那契數列指定位置的值 …

工作中Git常用命令

Git config --global user.name ‘sn’設置身份名字 Git config--global user.email “ 17909098592qq.com“ 設置郵箱 Git init 創建代碼倉庫 Ls -al 查看所有目錄 Git pull <遠程倉庫名> <遠程分支名>代碼更新 Git add file.txt 添加file.txt到git提交器 …

python的requests請求參數帶files

踩坑接口請求參數含文件 requests接口請求既有file&#xff0c;也有json。劃重點params requests 官網地址 https://requests.readthedocs.io/en/stable/user/quickstart/#post-a-multipart-encoded-file 接口請求既有file&#xff0c;也有json。劃重點params import reques…

kali一鍵部署各種環境和滲透工具

相信各位初入滲透領域的小伙們接觸了kali,但是苦于要配置各種環境,安裝kali沒有的工具,費時費力,博主有時候需要重新部署kali也很苦惱,所以編寫一鍵部署安裝kali腳本,下載地址在這里:https://download.csdn.net/download/weixin_59679023/88565320 配置流程: 1、找一…

Linux加強篇002-部署Linux系統

目錄 前言 1. shell語言 2. 執行命令的必備知識 3. 常用系統工作命令 4. 系統狀態檢測命令 5. 查找定位文件命令 6. 文本文件編輯命令 7. 文件目錄管理命令 前言 悟已往之不諫&#xff0c;知來者之可追。實迷途其未遠&#xff0c;覺今是而昨非。舟遙遙以輕飏&#xff…

Debian12試用報告

環境: win11vbox 虛擬機 網絡: host-only訪問局域網 nat 訪問外網, 配置為dhcp動態獲取ip 遇到的問題: 偶爾卡死: nat每次開機都不生效, 外網無法訪問; 開機后 重啟網絡可解決 sudo /etc/init.d/networking restart host-only倒是沒問題, 內網正常訪問 vim9還是用不習…

生產實踐:Redis與Mysql的數據強一致性方案

公眾號「架構成長指南」&#xff0c;專注于生產實踐、云原生、分布式系統、大數據技術分享。 數據庫和Redis如何保存強一致性&#xff0c;這篇文章告訴你 目的 Redis和Msql來保持數據同步&#xff0c;并且強一致&#xff0c;以此來提高對應接口的響應速度&#xff0c;剛開始考…

探索移動端可能性:Capacitor5.5.1和vue2在Android studio中精細融合

介紹&#xff1a; 移動應用開發是日益復雜的任務&#xff0c;本文將帶領您深入探索如何無縫集成Capacitor5.5.1、Vue2和Android Studio&#xff0c;以加速您的開發流程Capacitor 是一個用于構建跨平臺移動應用程序的開源框架。Vue 是一個流行的 JavaScript 框架&#xff0c;用…

多線程Thread(初階三:線程的狀態及線程安全)

目錄 一、線程的狀態 二、線程安全 一、線程的狀態 1.NEW Thread&#xff1a;對象創建好了&#xff0c;但是還沒有調用 start 方法在系統中創建線程。 2.TERMINATED&#xff1a; Thread 對象仍然存在,但是系統內部的線程已經執行完畢了。 3.RUNNABLE&#xff1a; 就緒狀態&…

雪花算法原理(設計原理、優缺點、如何改造它、以及應用)

雪花算法原理&#xff08;設計原理、優缺點、如何改造它、以及應用&#xff09; 雪花算法源碼為什么雪花算法是 64 位&#xff1f;為什么時間戳是41位&#xff1f;占雪花算法的 43-47 bit 位為什么工作臺最大只支持設置 31 &#xff1f;工作臺設置成了 63 會導致什么后果&#…

JeecgBoot3.0 漏洞升級 — 快速文檔

近幾年來&#xff0c;黑客攻擊行為呈現出日益復雜和隱蔽的趨勢&#xff0c;對個人和組織的安全造成了嚴重威脅。黑客們不斷尋找新的漏洞和安全漏洞&#xff0c;利用各種手段進行網絡攻擊&#xff0c;包括惡意軟件、網絡釣魚、勒索軟件等。因此&#xff0c;我們每個人都需要關注…

Java 之 final 詳解

目錄 一. 前言 二. final 的基礎使用 2.1. 修飾類 2.2. 修飾方法 2.2.1. private 方法是隱式的 final 2.2.2. final 方法可以被重載 2.3. 修飾參數 2.4. 修飾變量 2.4.1. static final 2.4.2. blank final 2.4.3. 所有 final 修飾的字段都是編譯期常量嗎&#xff1f…

數據結構:二叉查找樹,平衡二叉樹AVLTree,紅黑樹RBTree,平衡多路查找數B-Tree,B+Tree

二叉查找樹 二叉樹具有以下性質&#xff1a;左子樹的鍵值小于根的鍵值&#xff0c;右子樹的鍵值大于根的鍵值。 對該二叉樹的節點進行查找發現深度為1的節點的查找次數為1&#xff0c;深度為2的查找次數為2&#xff0c;深度為n的節點的查找次數為n&#xff0c;因此其平均查找次…

2023年亞太數學建模C題數據分享+詳細思路

在報名截止的前一天&#xff0c;我嘗試進行了報名。到那時&#xff0c;已有11,000個隊伍注冊參賽。在我的了解中&#xff0c;在數模比賽中除了國賽美賽外&#xff0c;幾乎沒有其他競賽的參賽隊伍數量能與此相媲美。即便不考慮賽題的難度和認可度&#xff0c;亞太地區的這場競賽…

JavaScript實現動態背景顏色

JavaScript實現動態背景顏色 前言實現過程HTML實現過程CSS實現過程JS實現過程全部源碼 前言 本文主要講解JavaScript如何實現動態背景顏色&#xff0c;可以根據顏色選擇器選擇的顏色而實時更新到背景中&#xff0c;如下圖所示。 當我們在顏色選擇器中改變顏色時&#xff0c;會…

代碼掃描,漏洞檢測

1) SQL注入是一種數據庫攻擊手段。攻擊者通過向應用程序提交惡意代碼來改變原SQL語句的含義&#xff0c;進而執行任意SQL命令&#xff0c;達到入侵數據庫乃至操作系統的目的。在Mybatis Mapper Xml中&#xff0c;#變量名稱創建參數化查詢SQL語句,不會導致SQL注入。而$變量名稱…

SPSS信度分析

前言&#xff1a; 本專欄參考教材為《SPSS22.0從入門到精通》&#xff0c;由于軟件版本原因&#xff0c;部分內容有所改變&#xff0c;為適應軟件版本的變化&#xff0c;特此創作此專欄便于大家學習。本專欄使用軟件為&#xff1a;SPSS25.0 本專欄所有的數據文件請點擊此鏈接下…