自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

五招幫你正確處理前任程序員留下的代碼

開發(fā) 前端
在這篇文章中,我們將通過我們可以使用的五種技術(shù)來確保將對人性的理解成為我們的優(yōu)勢,從現(xiàn)有代碼和原作者汲取盡可能多的幫助,并使得其他開發(fā)人員編寫的代碼最后變得比原來更優(yōu)秀。

作為軟件工程師不可避免會遇到的一個場景是:我們在改變或添加一個功能到不是我們創(chuàng)建的、我們不熟悉的、與我們負(fù)責(zé)的系統(tǒng)部分無關(guān)的代碼中時,會遇到麻煩。雖然這可能會是一個繁瑣而艱巨的任務(wù),但是由于使用其他開發(fā)人員編寫的代碼有很大的靈活性,所以我們可以從中得到大大的好處,包括增加我們的影響范圍,修復(fù)軟件腐爛以及學(xué)習(xí)我們以前不了解的系統(tǒng)部分(更何況,還可以學(xué)習(xí)其他程序員的技術(shù)和技巧)。

考慮到使用其他開發(fā)人員編寫的代碼既有其厭煩之處,又有其優(yōu)勢所在,所以我們必須小心不要犯一些嚴(yán)重的錯誤:

  • 我們的自我意識:我們可能會覺得自己知道得最多,但通常事實(shí)并非如此。我們要更改的是我們知之甚少的代碼——我們不知道原作者的意圖、導(dǎo)致此代碼的決策以及原作者在寫代碼時可用的工具和框架,等等。謙遜的品質(zhì)價值千金,你值得擁有。
  • 原作者的自我意識:我們即將接觸的代碼是由另一個開發(fā)人員所編寫的,另一種風(fēng)格、約束、期限和個人生活(消耗他或她工作之外的時間)。只有當(dāng)我們開始質(zhì)疑他或她做出的決定或質(zhì)疑代碼為什么這么不干凈的時候,那人才會自我反省,不至于夜郎自大。我們應(yīng)該盡一切努力讓原作者幫助我們工作,而不是妨礙我們。
  • 對未知的恐懼:很多時候,我們將要接觸的代碼是我們知之甚少或完全一無所知的。令人害怕的是:我們將對我們所做的任何改變負(fù)責(zé),但是我們基本上就像是在沒有光線的黑暗屋子里走動一樣。其實(shí)我們不需要擔(dān)心,而是應(yīng)該構(gòu)建一種使我們能夠在大小不一的改變中感到舒適的結(jié)構(gòu),并允許我們確保沒有破壞現(xiàn)有的功能。

由于開發(fā)人員,包括我們自己,是人,所以在處理其他開發(fā)人員編寫的代碼時,處理好很多人的天性問題是很有用的。在這篇文章中,我們將通過我們可以使用的五種技術(shù)來確保將對人性的理解成為我們的優(yōu)勢,從現(xiàn)有代碼和原作者汲取盡可能多的幫助,并使得其他開發(fā)人員編寫的代碼最后變得比原來更優(yōu)秀。雖然這里列出的5個方法并不全面,但是使用下面的技術(shù)將確保在結(jié)束改動其他開發(fā)人員編寫的代碼時,我們有信心保持現(xiàn)有功能的工作狀態(tài),同時確保我們的新功能與現(xiàn)有的代碼庫協(xié)調(diào)一致。

[[207973]]

1.確保測試的存在

要想確保在其他開發(fā)人員編寫的代碼中所存在的現(xiàn)有功能實(shí)際能夠按照預(yù)期的方式工作,并且我們對其進(jìn)行的任何更改都不會影響到功能的實(shí)現(xiàn),唯一真正令人信心十足的方式是用測試來支持代碼。當(dāng)我們遇到另一位開發(fā)人員編寫的代碼時,代碼有兩種所處的狀態(tài):(1)沒有足夠的測試水平,或(2)有足夠的測試水平。遇到前一種情況,我們得負(fù)責(zé)創(chuàng)建測試,而在后一種情況下,我們可以使用現(xiàn)有的測試來確保我們做出的任何更改都不會破壞代碼,并盡可能多地從測試去了解代碼的意圖。

創(chuàng)建新測試

這是一個悲傷的例子:我們在改變其他開發(fā)人員的代碼時,要對更改結(jié)果負(fù)責(zé),但是我們沒有辦法保證我們在進(jìn)行更改時不破壞任何東西。抱怨是沒有用的。無論我們發(fā)現(xiàn)代碼處在什么樣的條件下,我們總歸是要接觸代碼,因此如果代碼壞掉了,就是我們的責(zé)任。所以我們在改變代碼時,一定要掌控自己的行為。確定不會破壞代碼的唯一方法是自己寫測試。

雖然這是乏味的,但它允許我們通過編寫測試來學(xué)習(xí),這是它的主要優(yōu)點(diǎn)。假設(shè)代碼現(xiàn)在可以正常工作,而我們需要編寫測試,以便預(yù)期的輸入會導(dǎo)致預(yù)期的輸出。在我們完成這個測試的過程中,我們逐漸了解到代碼的意圖和功能。例如,給出以下代碼

 

  1. public class SuccessfulFilterTest { 
  2.     private static final double THRESHOLD_NET_SALARY = 68330.0; 
  3.     @Test 
  4.     public void under30AndNettingThresholdEnsureSuccessful() { 
  5.         Person person = new Person(29, THRESHOLD_NET_SALARY); 
  6.         Assert.assertTrue(new SuccessfulFilter().test(person)); 
  7.     } 
  8.     @Test 
  9.     public void exactly30AndNettingThresholdEnsureUnsuccessful() { 
  10.         Person person = new Person(30, THRESHOLD_NET_SALARY); 
  11.         Assert.assertFalse(new SuccessfulFilter().test(person)); 
  12.     } 
  13.     @Test 
  14.     public void under30AndNettingLessThanThresholdEnsureSuccessful() { 
  15.         Person person = new Person(29, THRESHOLD_NET_SALARY - 1); 
  16.         Assert.assertFalse(new SuccessfulFilter().test(person)); 
  17.     } 

我們對代碼的意圖以及為什么在代碼中使用Magic number知道得并不多,但是我們可以創(chuàng)建一組測試,已知輸入產(chǎn)生已知輸出。例如,通過做一些簡單的數(shù)學(xué)和解決構(gòu)成成功的閾值薪水問題,我們發(fā)現(xiàn)如果一個人的年齡在30歲以下,且每年大概賺68,330美元,那么他被認(rèn)為是成功的(按照本規(guī)范的標(biāo)準(zhǔn))。雖然我們不知道那些magic number是什么,但是我們知道它們確實(shí)減少了初始的薪水值。因此,68,330美元的閾值是扣除前的基本工資。通過使用這些信息,我們可以創(chuàng)建一些簡單的測試,例如:

 

  1. public class SuccessfulFilterTest { 
  2.     private static final double THRESHOLD_NET_SALARY = 68330.0; 
  3.     @Test 
  4.     public void under30AndNettingThresholdEnsureSuccessful() { 
  5.         Person person = new Person(29, THRESHOLD_NET_SALARY); 
  6.         Assert.assertTrue(new SuccessfulFilter().test(person)); 
  7.     } 
  8.     @Test 
  9.     public void exactly30AndNettingThresholdEnsureUnsuccessful() { 
  10.         Person person = new Person(30, THRESHOLD_NET_SALARY); 
  11.         Assert.assertFalse(new SuccessfulFilter().test(person)); 
  12.     } 
  13.     @Test 
  14.     public void under30AndNettingLessThanThresholdEnsureSuccessful() { 
  15.         Person person = new Person(29, THRESHOLD_NET_SALARY - 1); 
  16.         Assert.assertFalse(new SuccessfulFilter().test(person)); 
  17.     } 

通過這三個測試,我們現(xiàn)在對現(xiàn)有代碼的工作方式有了大致的了解:如果一個人不到30歲,且每年賺$ 68,300,那么他被認(rèn)為是成功人士。雖然我們可以創(chuàng)建更多的測試來確保臨界情況(例如空白年齡或工資)功能正常,但是一些簡短的測試不僅使我們了解了原始功能,還給出了一套自動化測試,可用于確保在對現(xiàn)有代碼進(jìn)行更改時,我們不會破壞現(xiàn)有功能。

使用現(xiàn)有測試

如果有足夠的代碼測試組件,那么我們可以從測試中學(xué)到很多東西。正如我們創(chuàng)建測試一樣,通過閱讀測試,我們可以了解代碼如何在功能層面上工作。此外,我們還可以知道原作者是如何讓代碼運(yùn)行的。即使測試是由原作者以外的人(在我們接觸之前)撰寫的,也依然能夠?yàn)槲覀兲峁╆P(guān)于其他人對代碼的看法。

雖然現(xiàn)有的測試可以提供幫助,但我們?nèi)匀恍枰獙Υ顺直A魬B(tài)度。測試是否與代碼的開發(fā)更改一起與時俱進(jìn)是很難說的。如果是的話,那么這是一個很好的理解基礎(chǔ);如果不是,那么我們要小心不要被誤導(dǎo)。例如,如果初始的工資閾值是每年75,000美元,而后來更改為我們的68,330美元,那么下面這個過時的測試可能會使我們誤入歧途:

 

  1. @Test 
  2. public void under30AndNettingThresholdEnsureSuccessful() { 
  3.     Person person = new Person(29, 75000.0); 
  4.     Assert.assertTrue(new SuccessfulFilter().test(person)); 

這個測試還是會通過的,但沒有了預(yù)期的作用。通過的原因不是因?yàn)樗檬情撝?,而是因?yàn)樗隽碎撝?。如果此測試組件包含這樣一個測試用例:當(dāng)薪水低于閾值1美元時,過濾器就返回false,這樣第二個測試將會失敗,表明閾值是錯誤的。如果套件沒有這樣的測試,那么陳舊的數(shù)據(jù)會很容易誤導(dǎo)我們弄錯代碼的真正意圖。當(dāng)有疑問時,請相信代碼:正如我們之前所表述的那樣,求解閾值表明測試沒有對準(zhǔn)實(shí)際閾值。

另外,要查看代碼和測試用例的存儲庫日志(即Git日志):如果代碼的最后更新日期比測試的最后更新日期更近(對代碼進(jìn)行了重大更改,例如更改閾值),則測試可能已經(jīng)過時,應(yīng)謹(jǐn)慎查看。注意,我們不應(yīng)該完全忽視測試,因?yàn)樗鼈円苍S仍然能為我們提供關(guān)于原作者(或最近撰寫測試的開發(fā)人員)意圖的一些文檔,但它們可能包含過時或不正確的數(shù)據(jù)。

2.與編寫代碼的人交流

在涉及多個人的任何工作中,溝通至關(guān)重要。無論是企業(yè),越野旅行還是軟件項目,缺乏溝通是損害任務(wù)最有效的手段之一。即使我們在創(chuàng)建新代碼時進(jìn)行溝通,但是當(dāng)我們接觸現(xiàn)有的代碼時,風(fēng)險會增加。因?yàn)榇藭r我們對現(xiàn)有的代碼并不太了解,因此我們所了解的內(nèi)容可能是被誤導(dǎo)的,或只代表了其中的一小部分。為了真正了解現(xiàn)有的代碼,我們需要和編寫它的人交流。

當(dāng)開始提出問題時,我們需要確定問題是具體的,并且旨在實(shí)現(xiàn)我們理解代碼的目標(biāo)。例如:

  • 這個代碼片段最適合放到系統(tǒng)的哪里?
  • 你有什么設(shè)計或圖表嗎?
  • 我應(yīng)該注意什么陷阱?
  • 這個組件或類是做什么的?
  • 有沒有什么你想放到代碼里,但當(dāng)時沒有做的?為什么?

始終要保持謙虛的態(tài)度,積極尋求原作者真正的答案。幾乎每個開發(fā)人員都碰到過這樣的場景,他或她看著別人的代碼,自問自答:“為什么他/她要這樣做?為什么他們不這樣做?”然后花幾個小時來得出本來只要原作者回答就能得到的結(jié)論。大多數(shù)開發(fā)人員都是有才華的程序員,所以即使如果我們遇到一個看似糟糕的決定,也有可能有一個很好的理由(可能沒有,但研究別人的代碼時最好假設(shè)他們這樣做是有原因的;如果真的沒有,我們可以通過重構(gòu)來改變)。

溝通在軟件開發(fā)中起次要副作用。1967年最初由Melvin Conway創(chuàng)立的康威定律規(guī)定:

  • 設(shè)計系統(tǒng)的任何組織…都將不可避免地產(chǎn)生一種設(shè)計,該設(shè)計結(jié)構(gòu)反映了組織的通信結(jié)構(gòu)。

這意味著,一個龐大、緊密溝通的團(tuán)隊可能會生成一體化,緊密耦合的代碼,但一些較小的團(tuán)隊可能會生成更獨(dú)立、松散耦合的代碼(有關(guān)此相關(guān)性的更多信息,請參閱《Demystifying Conway’s Law》)。對于我們來說,這意味著我們的通信結(jié)構(gòu)不僅影響特定的代碼段,也影響整個代碼庫。因此,與原作者密切溝通絕對是一個好辦法,但我們應(yīng)該自檢不要太過于依賴于原作者。這不僅可能會惹惱原作者,還可能在我們的代碼中產(chǎn)生無意識的耦合。

雖然這有助于我們深入研究代碼,但這是在假設(shè)可以接觸原作者的情況下。在很多時候,原作者可能已經(jīng)離開了公司,或恰巧不在公司(例如正在休假)。在此種情況下我們該做什么?詢問可能對代碼有所了解的人。這個人不一定要曾真正工作于代碼,他可以是在原作者編寫代碼時就在周圍,也可以是認(rèn)識原作者。哪怕僅是從原開發(fā)者周圍的人中得到只言片語,也可能會啟迪其他未知的代碼片段。

3.刪除所有警告

心理學(xué)中有一個眾所周知的概念,稱為“破窗理論”,Andrew Hunt和Dave Thomas在《 The Pragmatic Programmer 》(第4-6頁)中詳細(xì)描述了這個概念。這個理論最初是由James Q.Wilson和George L. Kelling提出的,描述如下:

假設(shè)有一個建筑物有幾扇破了的窗戶。如果窗戶沒有修好,那么破壞者會趨向于打破更多的窗戶。最終,他們甚至可能會破門而入,如果建筑物是沒人住的,那么他們可能會非法占有或者在里面點(diǎn)火。也可以考慮人行道的情況。如果道路上面有垃圾堆積,那么不久之后,就會有更多的垃圾累積。最終,人們甚至?xí)_始往那里扔外賣垃圾,甚至打破汽車。

這個理論指出,如果似乎已經(jīng)沒人關(guān)心這個物品或事物,那么我們就會忽視對物品或事物的照顧,這是人的天性。例如,如果一棟建筑物看上去已經(jīng)凌亂不堪,那么它更有可能被肆意破壞。在軟件方面,這個理論意味著如果開發(fā)人員發(fā)現(xiàn)代碼已經(jīng)是一團(tuán)糟,那么人的本性會讓他弄壞代碼。從本質(zhì)上說,我們心里想的是(即使心理活動沒有這么豐富),“既然最后一個人不在乎這代碼,我為什么要在乎?”或“都是亂糟糟的代碼,誰知道是誰寫的。”

但是,這不應(yīng)該成為我們的借口。只要我們接觸以前屬于其他人的代碼,那么我們就要對這些代碼負(fù)責(zé),并且如果它不能有效工作的話,我們得擔(dān)負(fù)后果。為了戰(zhàn)勝這種人的天性行為,我們需要采取一些小措施以避免我們的代碼更少地被弄臟(及時更換破掉的窗戶)。

一個簡單方法是刪除來自我們正在使用的整個包或模塊中的所有警告。至于未使用或添加注釋的代碼,刪除它。如果我們稍后需要這部分代碼,那么在存儲庫中,我們總是可以從先前的提交中檢索它。如果存在無法直接解決的警告(例如原始類型警告),那么使用@SuppressWarnings注解注釋該調(diào)用或方法。這樣可以確保我們對代碼進(jìn)行過仔細(xì)的考慮:它們不是因?yàn)槭韬龆l(fā)出的警告,而是我們明確地注意到了警告(如原始類型)。

一旦我們刪除或明確地禁止所有警告,那么我們就必須確保代碼保持免除警告。這有兩個主要作用:

  • 迫使我們仔細(xì)考慮我們創(chuàng)建的任何代碼。
  • 減少代碼腐敗的變化,現(xiàn)在的警告會導(dǎo)致以后的錯誤。

這對其他人,以及我們自己都有心理暗示作用——我們其實(shí)關(guān)心我們正在處理的代碼。它不再是條單行線——我們強(qiáng)逼著自己更改代碼,提交,然后永不回頭。相反,我們認(rèn)識到我們需要對這代碼負(fù)責(zé)。這對之后的軟件開發(fā)也是有幫助的——它向?qū)淼拈_發(fā)人員展示,這不是一間窗戶都破了的倉庫:而是一個維護(hù)良好的代碼庫。

4.重構(gòu)

在過去幾十年中,重構(gòu)已經(jīng)成為了一個非常重要的術(shù)語,并且最近被當(dāng)作是對當(dāng)前工作代碼做任何改變的代名詞。雖然重構(gòu)確實(shí)涉及對當(dāng)前正在工作的代碼的更改,但并非整個大局。Martin Fowler在他關(guān)于這個話題的重要著作——《Refactoring》一書中將重構(gòu)定義為:

  • 對軟件的內(nèi)部結(jié)構(gòu)進(jìn)行更改,使其更容易理解并且修改起來更便宜,而不改變其可觀察的行為。

這個定義的關(guān)鍵在于它涉及的更改不會改變系統(tǒng)可觀察的行為。這意味著當(dāng)我們重構(gòu)代碼時,我們必須要有方法來確保代碼的外部可見行為不會改變。在我們的例子中,這意味著是在我們繼承或自己開發(fā)的測試套件中。為了確保我們沒有改變系統(tǒng)的外部行為,每當(dāng)我們進(jìn)行改變時,都必須重新編譯和執(zhí)行我們的全部測試。

此外,并不是我們所做的每一個改變都被認(rèn)為是重構(gòu)。例如,重命名方法以更好地反映其預(yù)期用途是重構(gòu),但添加新功能不是。為了看到重構(gòu)的好處,我們將重構(gòu)SuccessfulFilter。執(zhí)行的第一個重構(gòu)是提取方法,以更好地封裝個人凈工資的邏輯:

 

  1. public class SuccessfulFilter implements Predicate<Person> { 
  2.     @Override 
  3.     public boolean test(Person person) { 
  4.         return person.getAge() < 30 && getNetSalary(person) > 60000; 
  5.     } 
  6.     private double getNetSalary(Person person) { 
  7.         return (((person.getSalary() - (250 * 12)) - 1500) * 0.94); 
  8.     } 

在我們進(jìn)行這種改變之后,我們重新編譯并運(yùn)行我們的測試套件,測試套件將繼續(xù)通過?,F(xiàn)在更容易看出,成功是通過一個人的年齡和凈薪酬定義的,但是getNetSalary方法似乎并不像Person類一樣屬于SuccessfulFilter(指示標(biāo)志就是該方法的唯一參數(shù)是Person,該方法的唯一調(diào)用是Person類的方法,因此對Person類有很強(qiáng)的親和力)。 為了更好地定位這個方法,我們執(zhí)行一個Move方法將其移動到Person類:

 

  1. public class Person { 
  2.     private int age; 
  3.     private double salary; 
  4.     public Person(int age, double salary) { 
  5.         this.age = age; 
  6.         this.salary = salary; 
  7.     } 
  8.     public void setAge(int age) { 
  9.         this.age = age; 
  10.     } 
  11.     public int getAge() { 
  12.         return age; 
  13.     } 
  14.     public void setSalary(double salary) { 
  15.         this.salary = salary; 
  16.     } 
  17.     public double getSalary() { 
  18.         return salary; 
  19.     } 
  20.     public double getNetSalary() { 
  21.         return ((getSalary() - (250 * 12)) - 1500) * 0.94; 
  22.     } 
  23. public class SuccessfulFilter implements Predicate<Person> { 
  24.     @Override 
  25.     public boolean test(Person person) { 
  26.         return person.getAge() < 30 && person.getNetSalary() > 60000; 
  27.     } 

為了進(jìn)一步清理此代碼,我們對每個magic number執(zhí)行符號常量替換magic number行為。為了知道這些值的含義,我們可能得和原作者交流,或者向具有足夠領(lǐng)域知識的人請教,以引領(lǐng)正確的方向。我們還將執(zhí)行更多的提取方法重構(gòu),以確保現(xiàn)有的方法盡可能簡單。

 

  1. public class Person { 
  2.     private static final int MONTHLY_BONUS = 250; 
  3.     private static final int YEARLY_BONUS = MONTHLY_BONUS * 12; 
  4.     private static final int YEARLY_BENEFITS_DEDUCTIONS = 1500; 
  5.     private static final double YEARLY_401K_CONTRIBUTION_PERCENT = 0.06; 
  6.     private static final double YEARLY_401K_CONTRIBUTION_MUTLIPLIER = 1 - YEARLY_401K_CONTRIBUTION_PERCENT; 
  7.     private int age; 
  8.     private double salary; 
  9.     public Person(int age, double salary) { 
  10.         this.age = age; 
  11.         this.salary = salary; 
  12.     } 
  13.     public void setAge(int age) { 
  14.         this.age = age; 
  15.     } 
  16.     public int getAge() { 
  17.         return age; 
  18.     } 
  19.     public void setSalary(double salary) { 
  20.         this.salary = salary; 
  21.     } 
  22.     public double getSalary() { 
  23.         return salary; 
  24.     } 
  25.     public double getNetSalary() { 
  26.         return getPostDeductionSalary(); 
  27.     } 
  28.     private double getPostDeductionSalary() { 
  29.         return getPostBenefitsSalary() * YEARLY_401K_CONTRIBUTION_MUTLIPLIER; 
  30.     } 
  31.     private double getPostBenefitsSalary() { 
  32.         return getSalary() - YEARLY_BONUS - YEARLY_BENEFITS_DEDUCTIONS; 
  33.     } 
  34. public class SuccessfulFilter implements Predicate<Person> { 
  35.     private static final int THRESHOLD_AGE = 30; 
  36.     private static final double THRESHOLD_SALARY = 60000.0; 
  37.     @Override 
  38.     public boolean test(Person person) { 
  39.         return person.getAge() < THRESHOLD_AGE && person.getNetSalary() > THRESHOLD_SALARY; 
  40.     } 

重新編譯和測試,發(fā)現(xiàn)系統(tǒng)仍然按照預(yù)期的方式工作:我們沒有改變外部行為,但是我們改進(jìn)了代碼的可靠性和內(nèi)部結(jié)構(gòu)。有關(guān)更復(fù)雜的重構(gòu)和重構(gòu)過程,請參閱Martin Fowler的Refactoring Guru網(wǎng)站。

5.當(dāng)你離開的時候,代碼比你發(fā)現(xiàn)它的時候更好

最后這個技術(shù)在概念上非常簡單,但在實(shí)踐中很困難:讓代碼比你發(fā)現(xiàn)它的時候更好。當(dāng)我們梳理代碼,特別是別人的代碼時,我們大多會添加功能,測試它,然后前行,不關(guān)心我們會不會貢獻(xiàn)軟件腐爛,也不在乎我們添加到類的新方法會不會導(dǎo)致額外的混亂。因此,本文的全部內(nèi)容可總結(jié)為以下規(guī)則:

  • 每當(dāng)我們修改代碼時,請確保當(dāng)你離開的時候,代碼比你發(fā)現(xiàn)它的時候更好。

前面提到過,我們需要對類造成的損壞和對改變的代碼負(fù)責(zé),如果它不能工作,那么修復(fù)是我們的職責(zé)。為了戰(zhàn)勝伴隨軟件生產(chǎn)而出現(xiàn)的熵,我們必須強(qiáng)制自己做到離開時的代碼比我們發(fā)現(xiàn)它的時候更佳。為了不逃避這個問題,我們必須償還技術(shù)債務(wù),確保下一個接觸代碼的人不需要再付出代價。說不定,將來可能是我們自己感謝自己這個時候的堅持呢。

責(zé)任編輯:未麗燕 來源: 碼農(nóng)網(wǎng)
相關(guān)推薦

2009-01-03 09:14:00

網(wǎng)絡(luò)模塊選購

2020-02-04 12:44:03

混合云架構(gòu)公共云

2015-09-24 10:06:59

2015-09-23 09:43:59

2010-08-25 15:24:13

職業(yè)定位

2024-08-06 08:00:00

SQL Query數(shù)據(jù)庫

2020-06-03 07:00:12

云成本監(jiān)控工具云散亂

2025-03-18 10:25:59

2019-12-20 08:00:00

云賬號劫持網(wǎng)絡(luò)釣魚云安全

2009-10-09 09:39:47

2018-03-22 12:44:13

2015-03-30 15:51:29

程序員程序員面試

2009-12-02 09:49:43

PHP Ajax亂碼

2015-07-13 11:32:09

PHP程序員正確姿勢

2015-03-10 11:34:22

SQL Server數(shù)據(jù)匯總ROUPBY

2010-01-20 11:09:18

虛擬服務(wù)器安全

2019-01-07 15:42:00

JavaScript前端 編碼

2010-05-21 11:07:55

MySQL 5 亂碼問

2014-11-28 09:45:35

程序員

2015-09-01 11:20:58

程序員糟糕代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號