亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Refactoring to DSL

flybywind / 1112人閱讀

摘要:為了消除重復(fù),可以將查找算法與比較準(zhǔn)則這兩個變化方向進(jìn)行分離。此刻,查找算法的方法名也應(yīng)該被重命名,使其保持在同一個抽象層次上。結(jié)構(gòu)性重復(fù)和存在結(jié)構(gòu)型重復(fù),需要進(jìn)一步消除重復(fù)。

Refactoring to DSL

OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathers

軟件設(shè)計的目標(biāo)

實現(xiàn)功能

易于重用

易于理解

沒有冗余

正交設(shè)計

軟件設(shè)計是一個「守破離」的過程。 -- 袁英杰

消除重復(fù)

分離變化方向

縮小依賴范圍

向穩(wěn)定的方向依賴

實戰(zhàn)

需求1: 存在一個學(xué)生的列表,查找一個年齡等于18歲的學(xué)生

快速實現(xiàn)
public static Student findByAge(Student[] students) {
 for (int i=0; i

上述實現(xiàn)存在很多設(shè)計的「壞味道」:

缺乏彈性參數(shù)類型:只支持?jǐn)?shù)組類型,List, Set都被拒之門外;

容易出錯:操作數(shù)組下標(biāo),往往引入不經(jīng)意的錯誤;

幻數(shù):硬編碼,將算法與配置高度耦合;

返回null:再次給用戶打開了犯錯的大門;

使用for-each

按照「最小依賴原則」,先隱藏數(shù)組下標(biāo)的實現(xiàn)細(xì)節(jié),使用for-each降低錯誤發(fā)生的可能性。

public static Student findByAge(Student[] students) {
 for (Student s : students)
   if (s.getAge() == 18)
     return s;
 return null;
}

需求2: 查找一個名字為horance的學(xué)生

重復(fù)設(shè)計

Copy-Paste是最快的實現(xiàn)方法,但會產(chǎn)生「重復(fù)設(shè)計」。

public static Student findByName(Student[] students) {
 for (Student s : students)
   if (s.getName().equals("horance"))
     return s;
 return null;
}

為了消除重復(fù),可以將「查找算法」與「比較準(zhǔn)則」這兩個「變化方向」進(jìn)行分離。

抽象準(zhǔn)則

首先將比較的準(zhǔn)則進(jìn)行抽象化,讓其獨立變化。

public interface StudentPredicate {
 boolean test(Student s);
}

將各個「變化原因」對象化,為此建立了兩個簡單的算子。

public class AgePredicate implements StudentPredicate {
 private int age;
 
 public AgePredicate(int age) {
   this.age = age;
 }
 
 @Override
 public boolean test(Student s) {
   return s.getAge() == age;
 }
}
public class NamePredicate implements StudentPredicate {
 private String name;
 
 public NamePredicate(String name) {
   this.name = name;
 }
 
 @Override
 public boolean test(Student s) {
   return s.getName().equals(name);
 }
}

此刻,查找算法的方法名也應(yīng)該被「重命名」,使其保持在同一個「抽象層次」上。

public static Student find(Student[] students, StudentPredicate p) {
 for (Student s : students)
   if (p.test(s))
     return s;
 return null;
}

客戶端的調(diào)用根據(jù)場景,提供算法的配置。

assertThat(find(students, new AgePredicate(18)), notNullValue());
assertThat(find(students, new NamePredicate("horance")), notNullValue());
結(jié)構(gòu)性重復(fù)

AgePredicateNamePredicate存在「結(jié)構(gòu)型重復(fù)」,需要進(jìn)一步消除重復(fù)。經(jīng)分析兩個類的存在無非是為了實現(xiàn)「閉包」的能力,可以使用lambda表達(dá)式,「Code As Data」,簡明扼要。

assertThat(find(students, s -> s.getAge() == 18), notNullValue());
assertThat(find(students, s -> s.getName().equals("horance")), notNullValue());
引入Iterable

按照「向穩(wěn)定的方向依賴」的原則,為了適應(yīng)諸如List, Set等多種數(shù)據(jù)結(jié)構(gòu),甚至包括原生的數(shù)組類型,可以將入?yún)⒅貥?gòu)為重構(gòu)為更加抽象的Iterable類型。

public static Student find(Iterable students, StudentPredicate p) {
 for (Student s : students)
   if (p.test(s))
     return s;
 return null;
}

需求3: 存在一個老師列表,查找第一個女老師

類型重復(fù)

按照既有的代碼結(jié)構(gòu),可以通過Copy Paste快速地實現(xiàn)這個功能。

public interface TeacherPredicate {
 boolean test(Teacher t);
}
public static Teacher find(Iterable teachers, TeacherPredicate p) {
 for (Teacher t : teachers)
   if (p.test(t))
     return t;
 return null;
}

用戶接口依然可以使用Lambda表達(dá)式。

assertThat(find(teachers, t -> t.female()), notNullValue());

如果使用Method Reference,可以進(jìn)一步地改善表達(dá)力。

assertThat(find(teachers, Teacher::female), notNullValue());
類型參數(shù)化

分析StudentMacher/TeacherPredicate, find(Iterable)/find(Iterable)的重復(fù),為此引入「類型參數(shù)化」的設(shè)計。

首先消除StudentPredicateTeacherPredicate的重復(fù)設(shè)計。

public interface Predicate {
 boolean test(E e);
}

再對find進(jìn)行類型參數(shù)化設(shè)計。

public static  E find(Iterable c, Predicate p) {
 for (E e : c)
   if (p.test(e))
     return e;
 return null;
}
型變

find的類型參數(shù)缺乏「型變」的能力,為此引入「型變」能力的支持,接口更加具有可復(fù)用性。

public static  E find(Iterable c, Predicate p) {
 for (E e : c)
   if (p.test(e))
     return e;
 return null;
}
復(fù)用lambda

Parameterize all the things.

觀察如下兩個測試用例,如果做到極致,可認(rèn)為兩個lambda表達(dá)式也是重復(fù)的。從「分離變化的方向」的角度分析,此lambda表達(dá)式承載的「比較算法」與「參數(shù)配置」兩個職責(zé),應(yīng)該對其進(jìn)行分離。

assertThat(find(students, s -> s.getName().equals("Horance")), notNullValue());
assertThat(find(students, s -> s.getName().equals("Tomas")), notNullValue());

可以通過「Static Factory Method」生產(chǎn)lambda表達(dá)式,將比較算法封裝起來;而配置參數(shù)通過引入「參數(shù)化」設(shè)計,將「邏輯」與「配置」分離,從而達(dá)到最大化的代碼復(fù)用。

public final class StudentPredicates {
 private StudentPredicates() {
 }
    
 public static Predicate age(int age) {
   return s -> s.getAge() == age;
 } 
 
 public static  Predicate name(String name) {
   return s -> s.getName().equals(name);
 }
}
import static StudentPredicates.*;
    
assertThat(find(students, name("horance")), notNullValue());
assertThat(find(students, age(10)), notNullValue());
組合查詢

但是,上述將lambda表達(dá)式封裝在Factory的設(shè)計是及其脆弱的。例如,增加如下的需求:

需求4: 查找年齡不等于18歲的女生

最簡單的方法就是往StudentPredicates不停地增加「Static Factory Method」,但這樣的設(shè)計嚴(yán)重違反了「OCP」(開放封閉)原則。

public final class StudentPredicates {
 ......
    
 public static Predicate ageEq(int age) {
   return s -> s.getAge() == age;
 } 
 
 public static Predicate ageNe(int age) {
   return s -> s.getAge() != age;
 } 
}

從需求看,比較準(zhǔn)則增加了眾多的語義,再次運用「分離變化方向」的原則,可發(fā)現(xiàn)存在兩類運算的規(guī)則:

比較運算:==, !=

邏輯運算:&&, ||

比較語義

先處理比較運算的變化方向,為此建立一個Matcher的抽象:

public interface Matcher {
 boolean matches(T actual);
   
 static  Matcher eq(T expected) {
   return actual -> expected.equals(actual);
 }
 
 static  Matcher ne(T expected) {
   return actual -> !expected.equals(actual);
 }
}

Composition everywhere.

此刻,age的設(shè)計運用了「函數(shù)式」的思維,其行為表現(xiàn)為「高階函數(shù)」的特性,通過函數(shù)的「組合式設(shè)計」完成功能的自由拼裝組合,簡單、直接、漂亮。

public final class StudentPredicates {
 ......
    
 public static Predicate age(Matcher m) {
   return s -> m.matches(s.getAge());
 }
}

查找年齡不等于18歲的學(xué)生,可以如此描述。

assertThat(find(students, age(ne(18))), notNullValue());
邏輯語義

為了使得邏輯「謂詞」變得更加人性化,可以引入「流式接口」的「DSL」設(shè)計,增強(qiáng)表達(dá)力。

public interface Predicate {
 boolean test(E e);
    
 default Predicate and(Predicate other) {
   return e -> test(e) && other.test(e);
 }
}

查找年齡不等于18歲的女生,可以表述為:

assertThat(find(students, age(ne(18)).and(Student::female)), notNullValue());
重復(fù)再現(xiàn)

仔細(xì)的讀者可能已經(jīng)發(fā)現(xiàn)了,StudentTeacher兩個類也存在「結(jié)構(gòu)型重復(fù)」的問題。

public class Student {
 public Student(String name, int age, boolean male) {
   this.name = name;
   this.age = age;
   this.male = male;
 }
 
 ......
 
 private String name;
 private int age;
 private boolean male;
}
public class Teacher {
 public Teacher(String name, int age, boolean male) {
   this.name = name;
   this.age = age;
   this.male = male;
 }
 
 ......
 
 private String name;
 private int age;
 private boolean male;
}
級聯(lián)反應(yīng)

StudentTeacher的結(jié)構(gòu)性重復(fù),導(dǎo)致StudentPredicatesTeacherPredicates也存在「結(jié)構(gòu)性重復(fù)」。

public final class StudentPredicates {
 ......
    
 public static Predicate age(Matcher m) {
   return s -> m.matches(s.getAge());
 }
}
public final class TeacherPredicates {
 ......
    
 public static Predicate age(Matcher m) {
   return t -> m.matches(t.getAge());
 }
}

為此需要進(jìn)一步消除重復(fù)。

提取基類

第一個直覺,通過「提取基類」的重構(gòu)方法,消除StudentTeacher的重復(fù)設(shè)計。

class Human {
 protected Human(String name, int age, boolean male) {
   this.name = name;
   this.age = age;
   this.male = male;
 }
   
 ...
 
 private String name;
 private int age;
 private boolean male;
}

從而實現(xiàn)了進(jìn)一步消除了StudentTeacher之間的重復(fù)設(shè)計。

public class Student extends Human {
 public Student(String name, int age, boolean male) {
   super(name, age, male);
 }
}
    
public class Teacher extends Human {
 public Teacher(String name, int age, boolean male) {
   super(name, age, male);
 }
}
類型界定

此時,可以通過引入「類型界定」的泛型設(shè)計,使得StudentPredicatesTeacherPredicates合二為一,進(jìn)一步消除重復(fù)設(shè)計。

public final class HumanPredicates {
 ......
 
 public static  
   Predicate age(Matcher m) {
   return s -> m.matches(s.getAge());
 } 
}
消滅繼承關(guān)系

StudentTeacher依然存在「結(jié)構(gòu)型重復(fù)」的問題,可以通過Static Factory Method的設(shè)計方法,并讓Human的構(gòu)造函數(shù)「私有化」,刪除StudentTeacher兩個子類,徹底消除兩者之間的「重復(fù)設(shè)計」。

public class Human {
 private Human(String name, int age, boolean male) {
   this.name = name;
   this.age = age;
   this.male = male;
 }
 
 public static Human student(String name, int age, boolean male) {
   return new Human(name, age, male);
 }
 
 public static Human teacher(String name, int age, boolean male) {
   return new Human(name, age, male);
 }
 
 ......
}
消滅類型界定

Human的重構(gòu),使得HumanPredicates的「類型界定」變得多余,從而進(jìn)一步簡化了設(shè)計。

public final class HumanPredicates {
 ......
 
 public static Predicate age(Matcher m) {
   return s -> m.matches(s.getAge());
 } 
}
絕不返回null

Billion-Dollar Mistake

在最開始,我們遺留了一個問題:find返回了null。用戶調(diào)用返回null的接口時,常常忘記null的檢查,導(dǎo)致在運行時發(fā)生NullPointerException異常。

按照「向穩(wěn)定的方向依賴」的原則,find的返回值應(yīng)該設(shè)計為Optional,使用「類型系統(tǒng)」的特長,取得如下方面的優(yōu)勢:

顯式地表達(dá)了不存在的語義;

編譯時保證錯誤的發(fā)生;

import java.util.Optional;
    
public  Optional find(Iterable c, Predicate p) {
 for (E e : c) {
   if (p.test(e)) {
     return Optional.of(e);
   }
 }
 return Optional.empty();
}
引入工廠
public interface Matcher {
 boolean matches(T actual);
   
 static  Matcher eq(T expected) {
   return actual -> expected.equals(actual);
 }
 
 static  Matcher ne(T expected) {
   return actual -> !expected.equals(actual);
 }
}

將所有的Static Factory方法都放在接口中,雖然簡單,也很自然。但如果方法之間產(chǎn)生重復(fù)代碼,需要「提取函數(shù)」,設(shè)計將變得非常不靈活,因為接口內(nèi)所有方法都將默認(rèn)為public,這往往不是我們所期望的,為此可以將這些Static Factory方法搬遷到Matchers實用類中去。

public final class Matchers {    
 public static  Matcher eq(T expected) {
   return actual -> expected.equals(actual);
 }
 
 public static  Matcher ne(T expected) {
   return actual -> !expected.equals(actual);
 }
 
 private Matchers() {
 }
}
實現(xiàn)大于

需求5: 查找年齡大于18歲的學(xué)生

assertThat(find(students, age(gt(18)).isPresent(), is(true));
public final class Matchers {
 ......
 
 public static > Matcher gt(T expected) {
   return actual -> Ordering.natural().compare(actual, expected) > 0;
 }
}

其中,natural代表了一種自然的比較規(guī)則。

public final class Ordering {
 public static > Comparator natural() {
   return (t1, t2) -> t1.compareTo(t2);
 }
}
實現(xiàn)小于

需求6: 查找年齡小于18歲的學(xué)生

assertThat(find(students, age(lt(18)).isPresent(), is(true));

依次類推,「小于」的規(guī)則實現(xiàn)如下:

public final class Matchers {
 ......
 
 public static > Matcher gt(T expected) {
   return actual -> Ordering.natural().compare(actual, expected) > 0;
 }
 
 public static > Matcher lt(T expected) {
   return actual -> Ordering.natural().compare(actual, expected) < 0;
 }
}
提取函數(shù)

設(shè)計產(chǎn)生了明顯的重復(fù),可以通過「提取函數(shù)」來消除重復(fù)。

public final class Matchers {
 ......
 
 public static > Matcher gt(T expected) {
   return actual -> compare(actual, expected) > 0;
 }
 
 public static > Matcher lt(T expected) {
   return actual -> compare(actual, expected) < 0;
 }
 
 private static > int compare(T actual, T expected) {
   return Ordering.natural().compare(actual, expected);
 }
}

其余比較操作,例如大于等于,小于等于的設(shè)計和實現(xiàn)依此類推,在此不再重述。

包含子串

需求7: 查找名字中包含horance的學(xué)生

assertThat(find(students, name(contains("horance")).isPresent(), is(true));
public final class Matchers {    
 ......
 
 public static Matcher contains(String substr) {
   return str -> str.contains(substr);
 }
}
子串開頭

需求8: 查找名字以horance開頭的學(xué)生

assertThat(find(students, name(starts("horance")).isPresent(), is(true));
public final class Matchers {    
 ......
 
 public static Matcher starts(String substr) {
   return str -> str.startsWith(substr);
 }
}

「子串結(jié)尾」的邏輯,可以設(shè)計ends的關(guān)鍵字,實現(xiàn)依此類推,在此不再重述。

不區(qū)分大小寫

需求9: 查找名字以horance開頭,但不區(qū)分大小寫的學(xué)生

assertThat(find(students, name(starts_ignoring_case("horance")).isPresent(), is(true));
public final class Matchers {    
 ......
    
 public static Matcher starts(String substr) {
   return str -> str.startsWith(substr);
 }
 
 public static Matcher starts_ignoring_case(String substr) {
   return str -> lower(str).startsWith(lower(substr));
 }
    
 private static String lower(String s) {
   return s.toLowerCase();
 }
}

startsstarts_ignoring_case之間存在微妙的重復(fù)設(shè)計,為此需要進(jìn)一步消除重復(fù)。

組合式設(shè)計
assertThat(find(students, name(ignoring_case(Matchers::starts, "Horance"))).isPresent(), is(true));

運用函數(shù)的「組合式設(shè)計」,達(dá)到代碼的最大可復(fù)用性。從OO的角度看,ignoring_case是對starts, ends, contains的功能增強(qiáng),是一種典型的「修飾」關(guān)系。

public static Matcher ignoring_case(
 Function> m, String substr) {
 return str -> m.apply(lower(substr)).matches(lower(str));
}

其中,Function>是一個一元函數(shù),參數(shù)為String,返回值為Matcher。

@FunctionalInterface
public interface Function {
   R apply(T t);
}
強(qiáng)迫用戶

雖然ignoring_case的設(shè)計高度可復(fù)用性,可由用戶根據(jù)實際情況,自由拼裝組合各種算子。但「方法引用」的語法,給用戶給造成了不必要的負(fù)擔(dān)。

assertThat(find(students, name(ignoring_case(Matchers::starts, "Horance"))).isPresent(), is(true));

可以提供starts_ignoring_case的語法糖,將用戶犯錯的幾率降至最低,但要保證實現(xiàn)不存在重復(fù)設(shè)計。

assertThat(find(students, name(starts_ignoring_case("Horance"))).isPresent(), is(true));

此時,ignoring_case也應(yīng)該重構(gòu)為private,變?yōu)橐粋€「可重用」的函數(shù)。

public static Matcher starts_ignoring_case(String substr) {
 return ignoring_case(Matchers::starts, substr);
}
     
private static Matcher ignoring_case(
 Function> m, String substr) {
 return str -> m.apply(lower(substr)).matches(lower(str));
}
修飾語義

需求13: 查找名字中不包含horance的第一個學(xué)生

assertThat(find(students, name(not_contains("horance")).isPresent(), is(true));
public final class Matchers {    
 ......
 
 public static Matcher not_contains(String substr) {
   return str -> !str.contains(substr);
 }
}

在這之前,也曾遇到過類似的「反義」的操作。例如,查找年齡不等于18歲的學(xué)生,可以如此描述。

assertThat(find(students, age(ne(18))).isPresent(), is(true));
public final class Matchers {    
 ......
 
 public static  Matcher ne(T expected) {
   return actual -> !expected.equals(actual);
 }
}

兩者對「反義」的描述存在兩份不同的表示,是一種隱晦的「重復(fù)設(shè)計」,需要一種巧妙的設(shè)計消除重復(fù)。

提取反義

為此,應(yīng)該刪除not_contains, ne的關(guān)鍵字,并提供統(tǒng)一的not關(guān)鍵字。

assertThat(find(students, name(not(contains("horance")))).isPresent(), is(true));

not的實現(xiàn)是一種「修飾」的手法,對既有的Matcher功能的增強(qiáng),巧妙地取得了「反義」功能。

public final class Matchers {    
 ......
 
 public static  Matcher not(Matcher matcher) {
   return actual -> !matcher.matches(actual);
 }
}
語法糖

對于not(eq(18))可以設(shè)計類似于not(18)的語法糖,使其更加簡單。

assertThat(find(students, age(not(18))).isPresent(), is(true));

其實現(xiàn)就是對eq的一種修飾操作。

public final class Matchers {    
 ......
 
 public static  Matcher not(T expected) {
   return not(eq(expected));
 }
}
邏輯或

需求13: 查找名字中包含horance,或者以liu結(jié)尾的學(xué)生

assertThat(find(students, name(anyof(contains("horance"), ends("liu")))).isPresent(), is(true));
public final class Matchers {    
 ......
 
 @SafeVarargs
 public static  Matcher anyof(Matcher... matchers) {
   return actual -> {
     for (Matcher matcher : matchers)
       if (matcher.matches(actual)) 
         return true;
     return false;
   };
 }
}
邏輯與

需求14: 查找名字中以horance開頭,并且以liu結(jié)尾的學(xué)生

assertThat(find(students, name(allof(starts("horance"), ends("liu")))).isPresent(), is(true));
public final class Matchers {    
 ......
 
 @SafeVarargs
 public static  Matcher allof(Matcher... matchers) {
   return actual -> {
     for (Matcher matcher : matchers)
       if (!matcher.matches(actual))
         return false;
     return true;
   };
 }
}
短路

allofanyof之間的實現(xiàn)存在重復(fù)設(shè)計,可以通過提取函數(shù)消除重復(fù)。

public final class Matchers {    
 ......
 
 @SafeVarargs
 private static  Matcher combine(
   boolean shortcut, Matcher... matchers) {
   return actual -> {
     for (Matcher matcher : matchers)
       if (matcher.matches(actual) == shortcut)
         return shortcut;
     return !shortcut;
   };
 }
 
 @SafeVarargs
 public static  Matcher allof(Matcher... matchers) {
   return combine(false, matchers);
 }
   
 @SafeVarargs
 public static  Matcher anyof(Matcher... matchers) {
   return combine(true, matchers);
 }
}
占位符

需求15: 查找算法始終失敗或成功

assertThat(find(students, age(always(false))).isPresent(), is(false));
public final class Matchers {    
 ......
 
 public static  Matcher always(boolean bool) {
   return e -> bool;
 }
}
回顧

通過15個需求的迭代和演進(jìn),通過運用「正交設(shè)計」和「組合式設(shè)計」的基本思想,得到了一套接口豐富、表達(dá)力極強(qiáng)的DSL

這一套簡單的DSL是一個高度可復(fù)用的Matcher集合,其設(shè)計既包含了OO的方法論,也涉及到了FP的思維,整體性設(shè)計保持高度的一致性和統(tǒng)一性。

鳴謝

「正交設(shè)計」的理論、原則、及其方法論出自前ThoughtWorks軟件大師「袁英杰」先生。英杰既是我的老師,也是我的摯友;其高深莫測的軟件設(shè)計的修為,及其對軟件設(shè)計獨特的哲學(xué)思維方式,是我等后輩學(xué)習(xí)的楷模。

思考

軟件設(shè)計的本質(zhì)是什么?

OOFP的本質(zhì)區(qū)別是什么?

組合式設(shè)計的精髓是什么?

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/65596.html

相關(guān)文章

  • Refactoring to Functions

    摘要:例如中的操作數(shù)據(jù)庫時,先取得數(shù)據(jù)庫的連接,操作數(shù)據(jù)后確保釋放連接當(dāng)操作文件時,先打開文件流,操作文件后確保關(guān)閉文件流。例如遍歷文件中所有行,并替換制定模式為其他的字符串。使用實現(xiàn)行為的參數(shù)化。 OO makes code understandable by encapsulating moving parting, but FP makes code understandable by...

    fuyi501 評論0 收藏0
  • Refactoring to collection(譯)

    摘要:獲取試讀文章高階函數(shù)高階函數(shù)就是參數(shù)為可以為,并且返回值也可為的函數(shù)。比方說,我們現(xiàn)在有顧客名單,但我們需要得到他們的郵箱地址我們現(xiàn)在不用高階函數(shù)用一個來實現(xiàn)它,代碼如下。 《Refactoring To Collection》 本文是翻譯Adam Wathan 的《Collection To Refactoring》的試讀篇章,這篇文章內(nèi)容不多,但是可以為我們Laraver使用者能更...

    lavnFan 評論0 收藏0
  • Debugging collections(譯)

    摘要:注本文是翻譯寫的關(guān)于調(diào)試技巧,讀完以后很實用,分享給大家閱讀過程中,翻譯有錯誤的希望大家指正原文鏈接最近我一直在使用的,如果你還不了解,我簡單說下一個集合就是一個功能強(qiáng)大的數(shù)組有很多強(qiáng)大處理其內(nèi)部數(shù)據(jù)的函數(shù)但是唯一讓我頭疼的地方是如何調(diào)試的 注:本文是翻譯Freek Van der Herten寫的關(guān)于Collection調(diào)試技巧,,讀完以后很實用,分享給大家.閱讀過程中,翻譯有錯誤的...

    lunaticf 評論0 收藏0
  • VS Code 配置

    摘要:配置安裝插件安裝包用于代碼的格式化將的安裝路徑添加到環(huán)境變量中。生成文件,編輯調(diào)試將文件中的改為,這樣無斷點時按或,直接運行程序不調(diào)試。方法提取選中要提取的表達(dá)式,,輸入或選擇命令??蓪x項設(shè)置快捷鍵。 VSCode Python 配置 安裝 Python 插件:ext install python 安裝包(用于 Python 代碼的格式化): pip install pep8 ...

    BothEyes1993 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<