2010年12月25日
每當(dāng)提到Exeption就會(huì)有人跳出來(lái)說(shuō)“Exception not use for flow control”,那到底是什么意思呢?什么情況下Exception就算控制流程了,什么時(shí)候就該拋出Exception了呢?
首先什么是Exception?
Definition: An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.
再看什么是“流程”?如果流程是指程序的每一步執(zhí)行,那異常就是控制流程的,它就是用來(lái)區(qū)分程序的正常流程和非正常流程的,從上面異常的定義就可以看出。因此為了明確我們應(yīng)該說(shuō)”不要用異常控制程序的正常流程“。如何定義正常流程和非正常流程很難,這是一個(gè)主觀的決定,沒(méi)有一個(gè)統(tǒng)一的標(biāo)準(zhǔn),只能根據(jù)實(shí)際情況。網(wǎng)上找個(gè)例子:
bool isDouble(string someString) {
try {
double d = Convert.ParseInt32(someString);
} catch(FormatException e) {
return false;
}
return true;
}
這個(gè)程序其實(shí)不是想convert數(shù)字,而是想知道一個(gè)字符串是否包含一個(gè)數(shù)字,通過(guò)判斷是不是有異常的方式來(lái)決定返回true還是false,這是個(gè)Smell,這種應(yīng)該算”異常控制了正常流程“。我們可以通過(guò)正則表達(dá)式或其他方式來(lái)判斷。
另外Clean Code上一個(gè)例子:
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
MealExpensesNotFound異常影響了正常的計(jì)算m_total的業(yè)務(wù)邏輯。對(duì)于這種情況可以通過(guò)一下方式改進(jìn):
public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// return the per diem default
}
}
以上兩個(gè)例子是比較明顯的異常控制正常流程,Smell很明顯,不會(huì)有很大爭(zhēng)議,但是實(shí)際情況中可能有很多例子沒(méi)有這么明顯,因?yàn)槎际侵饔^判定的。比如一下代碼,算不算異常控制正常流程?
public int doSomething()
{
doA();
try {
doB();
} catch (MyException e) {
return ERROR;
}
doC();
return SUCCESS;
}
看到這樣一段程序,如果沒(méi)有上下文,我們無(wú)法判斷。但是如果doSomething是想讓我們回答yes or no,success or error,我們不應(yīng)該通過(guò)有無(wú)異常來(lái)判斷yes or no,success or error,應(yīng)該有個(gè)單獨(dú)的方法來(lái)判斷,這個(gè)方法就只做這一件事情。如果doSometing是執(zhí)行一個(gè)操作,那么在這個(gè)過(guò)程中我們假定是不會(huì)出現(xiàn)問(wèn)題的,否則拋出異常是比較合理的。
1. Java Class Loading Mechanism
首先當(dāng)編譯一個(gè)Java文件時(shí),編譯器就會(huì)在生成的字節(jié)碼中內(nèi)置一個(gè)public,static,final的class字段,該字段屬于java.lang.Class類型,該class字段使用點(diǎn)來(lái)訪問(wèn),所以可以有:
java.lang.Class clazz = MyClass.class
當(dāng)class被JVM加載,就不再加載相同的class。class在JVM中通過(guò)(ClassLoader,Package,ClassName)來(lái)唯一決定。ClassLoader指定了一個(gè)class的scope,這意味著如果兩個(gè)相同的包下面的class被不同的ClassLoader加載,它們是不一樣的,并且不是type-compatible的。
JVM中所有的ClassLoader(bootstrap ClassLoader除外)都是直接或間接繼承于java.lang.ClassLoader抽象類,并且人為邏輯上指定了parent-child關(guān)系,實(shí)現(xiàn)上child不一定繼承于parent,我們也可以通過(guò)繼承它來(lái)實(shí)現(xiàn)自己的ClassLoader。
JVM ClassLoder架構(gòu),從上到下依次為parent-child關(guān)系:
- Bootstrap ClassLoader - 啟動(dòng)類加載器,主要負(fù)責(zé)加載核心Java類如java.lang.Object和其他運(yùn)行時(shí)所需class,位于JRE/lib目錄下或-Xbootclasspath指定的目錄。我們不知道過(guò)多的關(guān)于Bootstrap ClassLoader的細(xì)節(jié),因?yàn)樗且粋€(gè)native的實(shí)現(xiàn),不是Java實(shí)現(xiàn),所以不同JVMs的Bootstrap ClassLoader的行為也不盡相同。調(diào)用java.lang.String.getClassLoder() 返回null。
- sun.misc.ExtClassLoader - 擴(kuò)展類加載器,負(fù)責(zé)加載JRE/lib/ext目錄及-Djava.ext.dirs指定目錄。
- sun.misc.AppClassLoader - 應(yīng)用類加載器,負(fù)責(zé)加載java.class.path目錄
另外,還有一些其他的ClassLoader如:
java.net.URLClassLoader,java.security.SecureClassLoader,java.rmi.server.RMIClassLoader,sun.applet.AppletClassLoader
- 用戶還可以自己繼承java.lang.ClassLoader來(lái)實(shí)現(xiàn)自己的ClassLoader,用來(lái)動(dòng)態(tài)加載class文件。
ClassLoader特性:
- 每個(gè)ClassLoader維護(hù)一份自己的命名空間,同一個(gè)ClassLoader命名空間不能加載兩個(gè)同名的類。
- 為實(shí)現(xiàn)Java安全沙箱模型,默認(rèn)采用parent-child加載鏈結(jié)構(gòu),除Bootstrap ClassLoader沒(méi)有parent外,每個(gè)ClassLoader都有一個(gè)邏輯上的parent,就是加載這個(gè)ClassLoader的ClassLoader,因?yàn)镃lassLoader本身也是一個(gè)類,直接或間接的繼承java.lang.ClassLoader抽象類。
java.lang.Thread中包含一個(gè)public的方法public ClassLoader getContextClassLoader(),它返回某一線程相關(guān)的ClassLoader,該ClassLoader是線程的創(chuàng)建者提供的用來(lái)加載線程中運(yùn)行的classes和資源的。如果沒(méi)有顯式的設(shè)置其ClassLoader,默認(rèn)是parent線程的Context ClassLoader。Java默認(rèn)的線程上下文加載器是AppClassLoader。
ClassLoader工作原理:
了解ClassLoader工作原理,先來(lái)看一個(gè)ClassLoader類簡(jiǎn)化版的loadClass()方法源碼
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 try {
10 if (parent != null) {
11 c = parent.loadClass(name, false);
12 } else {
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
24 c = findClass(name);
25 }
26 }
27 if (resolve) {
28 resolveClass(c);
29 }
30 return c;
31 }
32 }
首先查看該class是否已被加載,如果已被加載則直接返回,否則調(diào)用parent的loadClass來(lái)加載,如果parent是null代表是Bootstrap ClassLoader,則有Bootstrap ClassLoader來(lái)加載,如果都未加載成功,最后由該ClassLoader自己加載。這種parent-child委派模型,保證了惡意的替換Java核心類不會(huì)發(fā)生,因?yàn)槿绻x了一個(gè)惡意java.lang.String,它首先會(huì)被JVM的Bootstrap ClassLoader加載自己JRE/lib下的,而不會(huì)加載惡意的。另外,Java允許同一package下的類可以訪問(wèn)受保護(hù)成員的訪問(wèn)權(quán)限,如定義一個(gè)java.lang.Bad,但是因?yàn)閖ava.lang.String由Bootstrap ClassLoader加載而java.lang.Bad由AppClassLoader加載,不是同一ClassLoader加載,仍不能訪問(wèn)。
2. Hotswap - 熱部署
即不重啟JVM,直接替換class。因?yàn)镃lassLoader特性,同一個(gè)ClassLoader命名空間不能加載兩個(gè)同名的類,所以在不重啟JVM的情況下,只能通過(guò)新的ClassLoader來(lái)重新load新的class。
1 public static void main(String[] args) throws InterruptedException, MalformedURLException {
2 IExample oldExample = new Example();
3 oldExample.plus();
4 System.out.println(oldExample.getCount());
5
6 Hotswap hotswap = new Hotswap();
7 while (true) {
8 IExample newExample = hotswap.swap(oldExample);
9 String message = newExample.message();
10 int count = newExample.plus();
11 System.out.println(message.concat(" : " + count));
12 oldExample = newExample;
13 Thread.sleep(5000);
14 }
15 }
16
利用hotswap替換就的Example,每5秒鐘輪詢一次,swap方法實(shí)現(xiàn)如下:
1 private IExample swap(IExample old) {
2 try {
3 String sourceFile = srcPath().concat("Example.java");
4 if (isChanged(sourceFile)) {
5 comiple(sourceFile, classPath());
6 MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL("file:"+classPath())});
7 Class<?> clazz = classLoader.loadClass("Example");
8 System.out.println(IExample.class.getClassLoader());
9 IExample exampleInstance = ((IExample) clazz.newInstance()).copy(old);
10 System.out.println(exampleInstance.getClass().getClassLoader());
11 return exampleInstance;
12 }
13 } catch ...
24 return old;
25 }
這里必須將exampleInstance轉(zhuǎn)型為IExample接口而不是Exmaple,否則會(huì)拋出ClassCastExecption,這是因?yàn)閟wap方法所在類Hotswap是有AppClassLoader加載的,而且加載Hotswap的同時(shí)會(huì)加載該類引用的Exmaple的symbol link,而Example是MyClassLoader加載的,不同的ClassLoader加載的類之間直接用會(huì)拋出ClassCastException, 在本例中ClassLoader實(shí)現(xiàn)如下:
1 public class MyClassLoader extends URLClassLoader {
2
3 public MyClassLoader(URL[] urls) {
4 super(urls);
5 }
6
7 @Override
8 public Class<?> loadClass(String name) throws ClassNotFoundException {
9 if ("Example".equals(name)) {
10 return findClass(name);
11 }
12 return super.loadClass(name);
13 }
14 }
而對(duì)IExample我們還是調(diào)用super的loadClass方法,該方法實(shí)現(xiàn)仍是JVM的parent-child委派方式,因此最終由AppClassLoader加載,加載Hotswap時(shí)加載的symbol link也是由AppClassLoader加載的,因此能夠成功。
此外再熱部署時(shí),被替換的類的所有引用及狀態(tài)都要遷移到新的類上,本例中只是很簡(jiǎn)單的調(diào)用copy函數(shù)遷移了count的狀態(tài)。
Tomcat的jsp熱部署機(jī)制就是基于ClassLoader實(shí)現(xiàn)的,對(duì)于其類的熱部署機(jī)制是通過(guò)修改內(nèi)存中的class字節(jié)碼實(shí)現(xiàn)的。
Resource:
1. java.lang.IllegalThreadStateException: process hasn't exited1 public static void main(String[] args) {
2 try {
3 Process process = Runtime.getRuntime().exec("javac");
4 System.out.println(process.exitValue());
5 } catch (IOException e) {
6 e.printStackTrace();
7 }
8 }
exec方法創(chuàng)建了一個(gè)native的進(jìn)程,并返回該process的對(duì)象,如果進(jìn)程還沒(méi)有返回,調(diào)用exitValue方法就會(huì)出現(xiàn)此異常,因?yàn)樵摲椒](méi)有阻塞,其實(shí)現(xiàn)如下:
1 public synchronized int exitValue() {
2 if (!hasExited) {
3 throw new IllegalThreadStateException("process hasn't exited");
4 }
5 return exitcode;
6 }
2. waitFor方法
1 public static void main(String[] args) {
2 try {
3 Process process = Runtime.getRuntime().exec("javac");
4 int result = process.waitFor();
5 System.out.println(result);
6 } catch (IOException e) {
7 e.printStackTrace();
8 } catch (InterruptedException e) {
9 e.printStackTrace();
10 }
11 }
waitFor方法會(huì)一直阻塞直到native進(jìn)程完成,并返回native進(jìn)程的執(zhí)行結(jié)果。如果native進(jìn)程無(wú)法執(zhí)行完成,waitFor方法將一直阻塞下去,其實(shí)現(xiàn)如下:
1 public synchronized int waitFor() throws InterruptedException {
2 while (!hasExited) {
3 wait();
4 }
5 return exitcode;
6 }
該程序在jdk1.7 windows下測(cè)試工作正常,返回2; 但是jdk1.4 windows下測(cè)試出現(xiàn)hang。JDK documention的解釋是
The methods that create processes may not work well for special processes on certain native platforms,
such as native windowing processes, daemon processes, Win16/DOS processes on Microsoft Windows,or shell scripts.
The created subprocess does not have its own terminal or console. All its standard io (i.e. stdin, stdout, stderr)
operations will be redirected to the parent process through three streams (getOutputStream(), getInputStream(),
getErrorStream()). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams,
failure to promptly write the input stream or read the output stream of the subprocess may cause
the subprocess to block, and even deadlock.
所以,出現(xiàn)hang時(shí),及時(shí)的flush標(biāo)準(zhǔn)輸入輸出或者錯(cuò)誤流能夠消除hang,如上面的javac,我們知道redirect到stderr中,所以解決hang后的代碼
1 public static void main(String[] args) {
2 try {
3 Process process = Runtime.getRuntime().exec("echo 'abc'>b.txt");
4 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
5 String line;
6 while((line=reader.readLine())!=null){
7 System.out.println(line);
8 }
9 int result = process.waitFor();
10 System.out.println(result);
11 } catch (IOException e) {
12 e.printStackTrace();
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 }
3. exec() is not a command line 并不是所有的command line命令都可以用exec 1 public static void main(String[] args) {
2 try {
3 Process process = Runtime.getRuntime().exec("echo 'abc'>a.txt");
4 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
5 String line;
6 while((line=reader.readLine())!=null){
7 System.out.println(line);
8 }
9 int result = process.waitFor();
10 System.out.println(result);
11 } catch (IOException e) {
12 e.printStackTrace();
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 }
結(jié)果為:
1 'abc'>a.txt
2 0
并沒(méi)有將創(chuàng)建a.txt,而從命令行執(zhí)行
"echo 'abc'>a.txt"卻正確創(chuàng)建了a.txt
Extreme programming is a set of simple and concrete practices than combine into an agile development process.
- Whole Team - Cutomers, managers and developers work closely with one another. So they are all aware of one another's problem and are collaborating to solve the problem. The customers can be a group of BAs, QAs or marketing persons in the same company as developers. The customers can be the paying customer. But in an XP project, the customer, however defined, is the member of and available to the team.
- User Stories - In order to plan a project, we must know something about the requirement, but we don't need to know very much. We need to know only enough about a requirement to estimate it. You may think that in order to estimate the requirement, you need to know all the details. That's not quite true. You have to know that there are details and you have to know roughly the kinds of details, but you don't have to know the sepecifics. Since the sepecific details of the requirements are likely to change with time, especially once the customers begin to see the system together. So when talking about requirements with the customer, we write some key words on a card to remind us of the conversation. A user story is memonic token of an ongoing conversation about a requirement. Every user story should have an estimation.
- Short Cycles - An XP project delivers working software every iteration - one or two weeks. At the end of each iteration, the sytem is demonstrated to the customer or the stakeholder in order to get their feedbacks. Every release there is a major delivery that can be put into production.
The Planning Game - The customers decide how important a feature is and the developers decide how much effort the feature will cost to implement. So the customers will select a collection of stories based on the priority and the previous iterations' velocity.
The Iteration Plan - Once an iteration has been started, the business agrees not to change the definition or priority of the stories in that iteration. The order of the stories within the iteration is a technical decision. The developers may work on the stories serially or concurrently. It depends but it's a technical decision.
The Release Plan - A release is usually three months. Similarly, the business or customer selecte collections of user stories and determines the priority based on their buget. But release are not cast in stone. The business can change or reorder at any time. They can write new stories, cancel stroies or change the priority. However, the business should not to change an interation that has been started.
Acceptance Tests - The detail about the user stories are captured in the form of acceptance tests specified by the customer. The acceptance tests are written by BAs and QAs immediately before or concurrently with the implementation of that story. They are written in a scripting form that allow them to be run automatically and repeatlly. These tests should be easy to read and understand for customers and business people. These tests become the true requirement document of the project. Once the acceptance test passes, it will be added the body of passing acceptance tests and is never allowed to fail. So the system is migrated from one working state to another.
Pair Programming - One memeber of each pair drives the keyboard and types the code. The other member of the pair watches the code being typed, finding errors and improvements.
The roles change frequently. If the driver gets tired or stuck, the pair grabs the keyboard and starts to drive. The keybord will move back and forth between them serval times in one hour. The resulting code is designed and authored by both memebers.
Pair memebership changes frequently. A reasonable goal is to change pair partner at least once per day. They should have worked on about everything that was going on in this iteration. Also it is very good for knowledge transfer.
Collective Ownership - A pair has right to check out any module and imporve it. No particular pair or developer are individually responsible for one particular module. Every developer of the project has fully responsible for each line of the code. So don't complain about the code may be written by the other pair. Let's try to improve it. Since it is our code.
Open Workspace - The team works together at one table or in one room. Each pair is within earshot of every other pair. Each has the opportunity to hear when another pair is in trouble. Each konws the state of the other. The sound in this room is a buzz of conversation. One might think that this would be a noise and distracting envrionment. But study suggested, working in a "war room" envrionment may increase productivity.
Simple Design - An XP team makes its designs as simple and expressive as they can be. Furthermore, the team narrows its focus to consider only the stories that are planned for the current iteration, not worrying about stories to come. The team migrates the design of the system from iteration to iteration to be the best design for the stories that the system currently implements.
- consider the simplest thing that could possibly work. - XP teams always try to find the simplest possible design option fot the current batch of stories.
- You aren't going to need it. - That means an XP team introduces one technology or one infrastructure before it is strictly needed. Yeah, but we know we are going to need that database one day. We are going to have to support multiple threads one day. So don't we need to put the hooks in for those things? The team puts the technology or infrastucture in only if it has proof or at least very compelling evidence. that putting it in now will be more cost-effective than waiting.
- Once and only once. XPers don't tolerate duplication of code. Wherever they find it. they remove it. The best way to remove redundancy is to create abstractions. After all, if two things are similar, some abstraction can be from them.
Continuous Integration
Sustainable Pace
Test-Driven Development
Refactoring
Metaphor
- 第一范式 - 列的原子性,數(shù)據(jù)庫(kù)表中的每一列都是不可再分的基本數(shù)據(jù)項(xiàng)
- 單一字段有多個(gè)有意義的值:比如 (people,address)其中address包括street,city,country,以逗號(hào)分割。想要查詢住在某一city的people很不容易
- 用很多字段來(lái)表示同一事實(shí):比如(people,address1,address2,address3),就算我們假設(shè)每個(gè)people最多有三個(gè)地址,當(dāng)想要查詢住在同一地址的people時(shí)也很不容易,因?yàn)橛锌赡躳eople1的address1與people2的address2相同,每一次都要比較3*3次組合
- 第二范式 - 符合第一范式;且表中的屬性必須完全依賴于全部主鍵,消除非主屬性對(duì)主鍵的部分依賴
- 比如(組件ID,供應(yīng)商ID,供應(yīng)商姓名,價(jià)格),組件ID+供應(yīng)商ID為主鍵,價(jià)格完全依賴于全部主鍵,因?yàn)椴煌M件不同供應(yīng)商具有不同價(jià)格,但是對(duì)于供應(yīng)商姓名,則只依賴于供應(yīng)商ID,會(huì)造成對(duì)同一供應(yīng)商ID,名字?jǐn)?shù)據(jù)重復(fù),而且如果供應(yīng)商改名,需要修改全部數(shù)據(jù)。因此需要單獨(dú)一個(gè)表(供應(yīng)商ID,供應(yīng)商姓名),(組件ID,供應(yīng)商ID,價(jià)格)
- 第三范式 - 非主屬性之間不能有依賴關(guān)系,必須都直接依賴于主屬性,消除傳遞依賴
- 比如(組件ID,制造商姓名,制造商地址),其中組件ID為主鍵,而制造商地址依賴于制造商姓名,需要(組件ID,制造商姓名)和新表(制造商姓名,制造商地址)其中姓名是主鍵
- 比如(訂單ID,組件ID,單價(jià),數(shù)量,總價(jià)),其中總價(jià)=單價(jià)*數(shù)量,總價(jià)依賴于單價(jià)和數(shù)量,需要去掉總價(jià)欄
- BC范式 - 任何屬性(包括非主屬性和主屬性)不能被非主屬性所決定。第三范式強(qiáng)調(diào)非主屬性不能依賴于其他非主屬性,BC范式是第三范式的加強(qiáng),強(qiáng)調(diào)“任何屬性”。因此如果滿足第三范式,并且只有一個(gè)主鍵,則一定滿足BC范式
一般,范式越高,表越多,數(shù)據(jù)庫(kù)操作時(shí)需要表關(guān)聯(lián),增加了查詢的復(fù)雜性,降低了查詢性能。因此并不是范式越高越好,要根據(jù)需要進(jìn)行權(quán)衡,第三范式已經(jīng)消除了大部分的數(shù)據(jù)冗余,插入異常,更新異常和刪除異常。
Why?Functional test characteristic
- The number of tests is mostly large.
- Page structure and elements may be changed frequently.
If our tests interact directly with the test driver (selenium,etc).
- UI element locators(Xpath, CSS, element Ids, names, etc) are copuled and repeat throughout the tests logic. It is hard to maintain, refactor and change especially when we change the page structure.
- The fine-grained UI operations hide our actual test intention. After some time we can not easily to identify what we want to do before.
What?A Page Object models the UI elements that your tests interact with as objects within the test code. It decouples the test logic from the fine-grained details of the UI page.
Test -> Page Object -> Test Driver
The driver is the actual executor of browser action, like click, select, type etc. The page object has knowledage of the HTML structure.
Advantages- When UI changes, only responding page object need to be changed. The tests logic will not be affected.
- It makes our tests logic simpler and more readable. Since the tests logic can only focus on its test secinaro rather than the structure of HTML page. As an example, think of login function, it only needs username and password, then do login action. How these are implemented shouldn't matter to the test. We don't care about that it uses a button or link to login.
- Complex interactions can be modeled as methods on the page object, which can be used in multiple times.
Best Practices- Public methods represent the services that the page offers
- Try not to expose the internals of the page. Like OO programming, object just expose the behaviour and hide its internal details.
- Generally don't make assertions, but it is better to do some kind of check to ensure the browser is actually on the page it should be. Since our following tests are all base on the assumption. It can be done by checking something simple like title.
- Methods return other page object. This means that we can effectively model the user's journey through our application.
- Don't need to represent the entire page. It can be just a tab or a navigation bar that your test interacts with.
- Only one place knows the HTML structure of a particular page. UI changes, the fix need only be applied in one place.
- Different results for the same action are modeled as different methods. Since the test knows the expected state.
1.
Small and the indent level should not be greater than one or two.
2.
Do One Thing - Functions should do one thing. They should do it well. They should do it only.
Steps of function are one level of abstraction below the name of the function. Then the function is doing one thing.
Another way to know function is doing more than one thing is if you can extract another function from it.
3.
One level of abstraction per function The stepdown rule - reading code from top to bottom.
switch
4.
Use Descriptive Names Don't be afraid to make a name long.
A long descriptive name is better than a short magic name.
A long descriptive name is better than a long descriptive comment.
Dont't be afraid to spend time choosing a name.
Be consistent in your names.
5.
Function Arguments The ideal number of arguments for a function is zero.
Three arguments should be avoided.
Flag Arguments - can be split into two function.
Aruments Objects - group the variables and abstract concept if possible.
Verbs and keywords - function and argument should form a very nice verb/noun pair.
6.
Have no side effects7.
Command Query Separation - Functions should either do something or answer something, but not both.
8.
Prefer Exceptions to returning error codes. Extract try/catch blocks, error handling is one thing.
9.
DRY - don't repeat yourself - especially the duplicated logic.
一,全局變量
1.全局變量可以被任何部分在任意時(shí)間改變,復(fù)雜化,降低可靠性
2.可能與子程序變量名相同,沖突可能導(dǎo)致程序無(wú)法運(yùn)行,難以調(diào)試
三種聲明全局變量模式
1.脫離任何函數(shù)的var foo = value
2.直接添加屬性至全局對(duì)象,全局對(duì)象是所有全局變量的容器,在web瀏覽器中全局對(duì)象名為window,window.foo = value
3.直接未經(jīng)聲明的變量 - 隱式的全局變量 foo = value
二,沒(méi)有塊作用域,有函數(shù)作用域
函數(shù)中定義的參數(shù)和變量在函數(shù)外部不可見(jiàn),而在一個(gè)函數(shù)中任何位置的定義的變量在該函數(shù)的任何地方可見(jiàn)。
function f1(){
var a=1;
function f2(){
if(false){
var a=2; //變量基于函數(shù),而非基于語(yǔ)句塊,沒(méi)有塊作用域
}
console.log(a); //undefined 未定義
}
f2();
console.log(a);//1
}
f1();
大多數(shù)語(yǔ)言中,一般聲明變量都是在第一次用到它的地方,在javascript中是一個(gè)壞習(xí)慣,因?yàn)闆](méi)有塊作用域,更好的是在每個(gè)函數(shù)開(kāi)頭聲明所有變量。
三,return語(yǔ)句返回一個(gè)值,值表達(dá)式必須和return在同一行上
return {
status:true
};
返回包含status成員元素的對(duì)象。
如果用
return
{
status:true
};
會(huì)返回undefined。
四,保留字不能用來(lái)命名變量或函數(shù),當(dāng)保留字被用作對(duì)象字面量的鍵值時(shí),必須用引號(hào),而且不能用點(diǎn)表示法,必須使用括號(hào)表示法。
var object = {case:value}; //非法
var object = {'case':value}; //ok
object.case = value; //非法
object['case'] = value; //ok
各瀏覽器對(duì)保留字使用限制上實(shí)現(xiàn)不同,以上語(yǔ)法在FF中合法,但其他瀏覽器不合法;再有不同的保留字行為也不相同。類似int/long/float等保留字在各瀏覽器中都可以做變量名及對(duì)象字面量的鍵值。但不建議使用任何保留字。
五,typeof - 識(shí)別運(yùn)算數(shù)類型的字符串
但typeof null返回'object',更好的檢測(cè)null的方法: my_value === null
對(duì)于正則表達(dá)式 typeof /a/,一些瀏覽器返回'object',另一些返回'function'
六,parseInt - 將字符串轉(zhuǎn)換為整數(shù)
1.此函數(shù)遇到非數(shù)字時(shí)就停止解析,即parseInt('16')和parseInt('16 abc')產(chǎn)生結(jié)果相同,都是16.
2.如果字符串第一個(gè)字符是0,則基于八進(jìn)制解析,而不是十進(jìn)制。八進(jìn)制中沒(méi)有8,9數(shù)字,所以parseInt('08')和parseInt('09')結(jié)果為0。此函數(shù)可以接收一個(gè)基數(shù)作為參數(shù),parseInt('08',10)結(jié)果為8。
七,浮點(diǎn)數(shù)
0.1+0.2不等于0.3
八,NaN
function isNumber(value){
return typeof value === 'number' && isFinite(value);
}
isFinite篩調(diào)NaN和Infinity,但是isFinite會(huì)試圖把它的運(yùn)算數(shù)轉(zhuǎn)換為一個(gè)數(shù)字,如果運(yùn)算數(shù)事實(shí)上不是一個(gè)數(shù)字,就會(huì)報(bào)錯(cuò),所以加上typeof value === 'number'。
九,假值
0,NaN,'',false,null,undefined全部等于假,但它們是不可互換的。
印象中美國(guó)人追求自由,但他們開(kāi)會(huì)時(shí)卻是嚴(yán)肅認(rèn)真,說(shuō)到開(kāi)會(huì),他們有一本厚厚的開(kāi)會(huì)規(guī)則-羅伯特議事規(guī)則。作者亨利.馬丁.羅伯特畢業(yè)于西點(diǎn)軍校,有一次奉命主持一個(gè)地方教會(huì)的會(huì)議,結(jié)果人們爭(zhēng)個(gè)不亦樂(lè)乎,什么結(jié)論都沒(méi)有,搞砸啦。于是羅伯特研究出了一些規(guī)則,最初只有四五條,放在錢包里,開(kāi)會(huì)時(shí)遵守這些規(guī)則,就能順利成功。后來(lái)1876年羅伯特寫(xiě)了《通用議事規(guī)則》。至今發(fā)行五百萬(wàn)冊(cè),羅伯特議事規(guī)則已取得最廣泛的認(rèn)可和應(yīng)用,它的根本意圖在于使一個(gè)會(huì)議能以盡可能最有效的方式達(dá)到它的目的。
話說(shuō)羅伯特議事規(guī)則初現(xiàn)中國(guó)是在組織農(nóng)村開(kāi)會(huì)時(shí),當(dāng)時(shí)會(huì)議及其混亂,表現(xiàn)為:
一,你說(shuō)周杰倫,我引出周潤(rùn)發(fā),東一榔頭西一棒子,最后忘了開(kāi)會(huì)的目的是啥,還可能對(duì)于已經(jīng)討論表決的問(wèn)題,又重新拿來(lái)說(shuō),反反復(fù)復(fù),歸結(jié)起來(lái)就是跑題;
二,領(lǐng)導(dǎo)或者權(quán)威講個(gè)不停,下面人睡著啦,歸結(jié)為一家言;
三,爭(zhēng)論起來(lái),互相指責(zé),甚至人身攻擊,歸結(jié)為野蠻爭(zhēng)論。
其實(shí)這三條也是我們?nèi)粘?huì)議經(jīng)常出現(xiàn)的問(wèn)題。
首先說(shuō)野蠻爭(zhēng)論問(wèn)題,很容易解決,羅伯特議事規(guī)則限制只對(duì)會(huì)議討論的問(wèn)題發(fā)言,不允許評(píng)議個(gè)人,發(fā)言時(shí)對(duì)主持人進(jìn)行陳述,而非辯論對(duì)方。總結(jié)起來(lái),用中國(guó)言語(yǔ)來(lái)說(shuō)就是“對(duì)事不對(duì)人”,因?yàn)橹肛?zé)個(gè)人并不能解決問(wèn)題,不允許指責(zé)一個(gè)會(huì)員的動(dòng)機(jī),動(dòng)機(jī)很難衡量,但可以用強(qiáng)烈的語(yǔ)言譴責(zé)一個(gè)議案的性質(zhì)或后果。
第二對(duì)于一家言問(wèn)題,羅伯特議事規(guī)則限制每個(gè)人發(fā)言的次數(shù)和時(shí)間用以避免一家言問(wèn)題。其實(shí)這是個(gè)人智慧和經(jīng)驗(yàn)與集體合作的關(guān)系,仍用中國(guó)言語(yǔ)來(lái)說(shuō)“三個(gè)臭皮匠頂個(gè)諸葛亮”。對(duì)于睡著問(wèn)題,可能是由于發(fā)言人太愛(ài)講,可以通過(guò)前述方法解決,還可能是由于當(dāng)前話題不感興趣甚至無(wú)關(guān),則可直接離開(kāi)會(huì)議或不參加。
第三對(duì)于跑題問(wèn)題,羅伯特議事規(guī)則規(guī)定會(huì)議討論的內(nèi)容應(yīng)當(dāng)是一個(gè)明確的動(dòng)議:“動(dòng)議,動(dòng)議,就是行動(dòng)的建議!”動(dòng)議必須是具體的,明確的,可操作的行動(dòng)建議,每一個(gè)動(dòng)議都會(huì)導(dǎo)致一個(gè)action。比如“早上9點(diǎn)上班”,而不是“早上幾點(diǎn)上班?”。發(fā)言人應(yīng)首先表明觀點(diǎn),贊成還是反對(duì),并陳述理由。與本次會(huì)議動(dòng)議無(wú)關(guān)的其他動(dòng)議,應(yīng)組織其他會(huì)議,即我們常說(shuō)的“一事一議”。經(jīng)雙方輪流發(fā)言后,主持人組織投票。對(duì)于已經(jīng)表決的觀點(diǎn),除非理由充分,并經(jīng)所有成員2/3同意再議,再組織再次討論。
對(duì)于第三條,我覺(jué)得首先需要把平時(shí)會(huì)議劃分一下幾類:
1.session:做session的人事先準(zhǔn)備好內(nèi)容,并計(jì)劃和把握session時(shí)長(zhǎng),同樣中間遇到與session無(wú)關(guān)的其他問(wèn)題或者非常detail的問(wèn)題,做session的人要打斷討論,對(duì)于重要問(wèn)題組織其他session,對(duì)于detail問(wèn)題offline討論。
2.discussion:如羅伯特議事規(guī)則所說(shuō),組織discussion的人要有明確的動(dòng)議,支持方與反對(duì)方輪流發(fā)言,如果規(guī)定時(shí)間未達(dá)成一致,組織者須組織投票。
3.brainstorm:通過(guò)集思廣益、發(fā)揮集體智慧,迅速地獲得大量的新設(shè)想與創(chuàng)意,這是一種極為有效的開(kāi)啟創(chuàng)新思維的方法。頭腦風(fēng)暴結(jié)束后才開(kāi)始對(duì)觀點(diǎn)進(jìn)行討論,那針對(duì)每一個(gè)討論,又是一個(gè)discussion。
另外,并不是所有會(huì)議都必須所有人參加,如果感覺(jué)對(duì)自己影響不大或者有比會(huì)議更要緊的事情可以不參加,或中間離席。
1. Change to oracle user:$
su oracle2. Log in as sys user: $
sqlplus sys/password as sysdba3. Find out flash recovery area usage info, from where we can find the archive log space usage percent: SQL>
select * from V$FLASH_RECOVERY_AREA_USAGE;4. Caculate the space used by flash recovery area:SQL>
select sum(percent_space_used)*3/100 from V$FLASH_RECOVERY_AREA_USAGE;
5. Show Archive log location: SQL>
show parameter log_archive_dest;6. Show recovery area location: SQL>
show parameter recover;7. Delete or move some old archive log files: SQL>
rm -rf archive_log_2011_06_0*;Notice: After step 7, we must use RMAN to maintain the control files, otherwise, the deletion changes can not be apply.8. Log in as RMAN: SQL>
rman target sys/password;9. Find out unused archive log: SQL>
crosscheck archive log all;10. Delete expired archive log files: SQL>
delete expired archivelog all;11. Check the flash recovery area usage info again, make sure archive log files have been deleted: SQL>
select * from V$FLASH_RECOVERY_AREA_USAGE;
12. Finally, increase the recovery file destination size: SQL>
alter system set db_recovery_file_dest_size=3G scope=both;
偶爾想起學(xué)生時(shí),幾個(gè)同學(xué)一起做項(xiàng)目,雖然不大,但還是分了模塊,每個(gè)人負(fù)責(zé)一個(gè)或多個(gè)模塊。每天打開(kāi)電腦,從版本控制庫(kù)更新最新代碼,運(yùn)行下程序,回憶昨天完成的并計(jì)劃今天的任務(wù)。突然發(fā)現(xiàn)昨天好好的功能,今天突然不work甚至拋出異常,于是大喊一聲誰(shuí)破壞了我的功能?沒(méi)人回應(yīng),只能自己一步一步查看到底那個(gè)代碼破壞了昨天的功能,然后找到提交的同學(xué),再商量到底該怎么解決。其實(shí)當(dāng)時(shí)我們各自實(shí)現(xiàn)自己的模塊,寫(xiě)(或不寫(xiě))測(cè)試,然后提交代碼,各自只關(guān)心和測(cè)試自己的模塊,難免會(huì)發(fā)生沖突,尤其是兩個(gè)模塊結(jié)合的地方,如果被破壞方可能在幾個(gè)小時(shí),幾天甚至更長(zhǎng)時(shí)間后發(fā)覺(jué)應(yīng)用被破壞,或者直到最后項(xiàng)目上線前一分鐘才發(fā)覺(jué)。。。
后來(lái)參加工作,知道了持續(xù)集成可以解決當(dāng)時(shí)的痛苦,而且可以提供更多。持續(xù)集成是XP實(shí)踐之一,于是,很多人錯(cuò)誤認(rèn)為持續(xù)集成是為了實(shí)現(xiàn)敏捷編程的。實(shí)際上,早于敏捷編程概念的提出,持續(xù)集成作為一個(gè)best practice就已經(jīng)被很多公司采用了,只不過(guò)作為一個(gè)概念,則是由Martin大叔為了推進(jìn)敏捷所倡導(dǎo)并由此風(fēng)靡起來(lái)。
那么為什么我們需要持續(xù)集成或者說(shuō)持續(xù)集成給我?guī)?lái)了什么好處呢?
- 及早獲得系統(tǒng)級(jí)的成果或者是可以deploy到production環(huán)境的產(chǎn)品。因?yàn)榇a已經(jīng)被集成起來(lái)了,即使整個(gè)系統(tǒng)還不是那么可用,至少比零散的模塊讓人更有安全感。增強(qiáng)了客戶和我們自己的信心。
- 可以很容易的知道每一個(gè)版本之間有什么變化,甚至我們可以很容易的重新build出任何一個(gè)時(shí)間點(diǎn)上的版本,這點(diǎn)非常重要。工作后第一個(gè)項(xiàng)目就遇到了難纏的客戶,經(jīng)常今天告知我們明天需要一個(gè)版本部署到生產(chǎn)環(huán)境,如果沒(méi)有持續(xù)集成,我們?nèi)绾蔚玫接行判目梢圆渴鸬絧roduction的build?如果沒(méi)有持續(xù)集成,我們?nèi)绾翁峁┌钚鹿δ艿目梢圆渴鸬絧roduction的build?偶爾,客戶并不需要你提供的最終build,而是包含功能1和2而不包含功能3的build,如果沒(méi)有頻繁的持續(xù)集成,我們又怎么快速的得到某一時(shí)間的build?
- 及早的發(fā)現(xiàn)和定位錯(cuò)誤:比如學(xué)生時(shí)代的項(xiàng)目,某個(gè)人在工作的時(shí)候踩進(jìn)了別人的領(lǐng)域、影響了別人的代碼,而被影響的人還不知道發(fā)生了什么,于是bug就出現(xiàn)了。這種bug是最難查的,因?yàn)閱?wèn)題不是出在某一個(gè)人的領(lǐng)域里,而是出在兩個(gè)人的交流上面。隨著時(shí)間的推移,問(wèn)題會(huì)逐漸惡化。通常,在集成階段出現(xiàn)的bug早在幾周甚至幾個(gè)月之前就已經(jīng)存在了。結(jié)果,開(kāi)發(fā)者需要在集成階段耗費(fèi)指數(shù)倍的時(shí)間和精力來(lái)尋找這些bug的根源。如果有了持續(xù)集成,當(dāng)持續(xù)集成失敗了,很容易說(shuō)明最新加入的代碼或者修改的代碼引起了錯(cuò)誤,當(dāng)然這需要強(qiáng)大的自動(dòng)化測(cè)試作為后盾。
- 項(xiàng)目進(jìn)度的一目了然。頻繁的集成使我們可以看到哪些功能可以使用,哪些功能還沒(méi)有實(shí)現(xiàn)。開(kāi)發(fā)人員不用在匯報(bào)任務(wù)的時(shí)候說(shuō)我完成了多少百分比而煩惱,而PM也不再煩惱程序員說(shuō)完成了編碼的50%到底是個(gè)什么概念。
因此引用同事的原話“沒(méi)有持續(xù)集成,說(shuō)明交付流程是混亂不清晰隨機(jī)的。”
1.函數(shù)就是對(duì)象
函數(shù)可以存放變量,數(shù)組,對(duì)象
函數(shù)可以當(dāng)做參數(shù)傳遞給其他函數(shù)
函數(shù)可以返回函數(shù)
函數(shù)可以擁有方法
2.函數(shù)的調(diào)用
調(diào)用運(yùn)算符是任何一個(gè)產(chǎn)生函數(shù)值的表達(dá)式之后的一對(duì)圓括號(hào)。
調(diào)用一個(gè)函數(shù)將暫停當(dāng)前函數(shù)的執(zhí)行,傳遞控制權(quán)和參數(shù)給新函數(shù),除了聲明時(shí)定義的形參,每個(gè)函數(shù)接收兩個(gè)附加的參數(shù)-this和arguments。
this的值取決于調(diào)用的模式:
方法調(diào)用模式:函數(shù)被保存為對(duì)象的一個(gè)屬性時(shí),稱為方法。方法被調(diào)用時(shí),this綁定到該對(duì)象。
var obj={
value:0,
increment:function(){
this.value+=1;
}
}
obj.increment();
obj.value;
函數(shù)調(diào)用模式:當(dāng)一個(gè)函數(shù)并非對(duì)象的屬性時(shí),它被當(dāng)作一個(gè)函數(shù)來(lái)調(diào)用,this綁定到全局對(duì)象(Window),因此不能共享外部函數(shù)對(duì)對(duì)象的訪問(wèn)權(quán)-語(yǔ)言設(shè)計(jì)的錯(cuò)誤。當(dāng)內(nèi)部函數(shù)被調(diào)用時(shí),this應(yīng)該仍綁定到外部函數(shù)的this變量。
解決方案:定義一個(gè)變量并給它賦值為this
var myObj={
value: 0,
getValue: function(){
return this.value;
}
};
myObj.test=function(){
var that = this
var helper=function(){
that.value+=1;
}();
}
myObj.test();
構(gòu)造器調(diào)用模式:如果一個(gè)函數(shù)前面帶上new來(lái)調(diào)用,將會(huì)創(chuàng)建一個(gè)隱藏連接到該函數(shù)的prototype成員的新對(duì)象,this將會(huì)綁定到新對(duì)象上。
var Quo = function(string){
this.status=string;
}
Quo.prototype.get_status=function(){
return this.status;
}
var myQuo=new Quo("confused");
myQuo.get_status();
apply調(diào)用模式:apply方法讓我們構(gòu)建一個(gè)參數(shù)數(shù)組并用其去調(diào)用函數(shù)。該方法允許我們選擇this的值,apply方法接收兩個(gè)參數(shù),第一個(gè)就是將被綁定給this的值,如果是null則為Window對(duì)象,第二個(gè)是參數(shù)列表。
var array=[3,4];
var sum=add.apply(null,array);
var Quo = function(string){
this.status=string;
}
Quo.prototype.get_status=function(){
return this.status;
}
var statusObj={
status:"OK"
};
Quo.prototype.get_status.apply(statusObj);
var myObj={
add:function(a,b){
//參數(shù)為null或#ff0000,this引用為Window, double函數(shù)的alert證明了這一點(diǎn)
alert(this.location.href);
return a+b;
}
}
var array=[3,4];
var sum=myObj.add.apply(null,array);
2.給類型增加方法:待續(xù)
3.閉包-能夠讀/寫(xiě)函數(shù)內(nèi)部的某些變量的子函數(shù),并將這些變量保存在內(nèi)存中.
討論閉包之前先討論函數(shù)作用域
1)函數(shù)內(nèi)的局部變量在函數(shù)外部無(wú)法訪問(wèn)。
function f(){
var n=1;
}
alert(n);//error
2)內(nèi)部函數(shù)可以訪問(wèn)外部函數(shù)的局部變量。
function f(){
var n=1;
function inner(){
alert(n);
}
}
因此只要將inner作為返回值,就可以在f外部讀取它的內(nèi)部變量了。
function f(){
var n=1;
function inner(){
alert(n);
}
return inner;
}
f()();
閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。如果把父函數(shù)當(dāng)作對(duì)象使用,把閉包當(dāng)作它的公共函數(shù),把內(nèi)部變量當(dāng)作它的私有屬性。
理解內(nèi)部函數(shù)能訪問(wèn)外部函數(shù)的實(shí)際變量而無(wú)須復(fù)制:
var add_handles=function(nodes){
for(var i=0;i<nodes.length;i++){
nodes[i].onclick=function(e){
alert(i);
}
}
}
var add_handles=function(nodes){
for(var i=0;i<nodes.length;i++){
nodes[i].onclick=function(i){
return function(e){
alert(e);
}
}(i);
}
}
閉包用途:
1)讀取函數(shù)內(nèi)部的局部變量
2)將變量的值始終保存在內(nèi)存中,因?yàn)閒是inner的父函數(shù),而inner被賦給了一個(gè)全局變量,導(dǎo)致inner始終在內(nèi)存中,而inner的存在依賴于f,因此f也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后被垃圾回收機(jī)制回收。
3)通過(guò)保護(hù)變量的安全實(shí)現(xiàn)JS私有屬性和私有方法(不能被外部訪問(wèn))
缺點(diǎn):閉包使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,不能濫用閉包。
4.模塊
一個(gè)定義了私有變量和函數(shù)的函數(shù),利用閉包創(chuàng)建可以訪問(wèn)私有變量和函數(shù)的特權(quán)函數(shù),然后返回這個(gè)特權(quán)函數(shù)。
提供接口卻隱藏狀態(tài)與實(shí)現(xiàn)的函數(shù)或?qū)ο蟆^饤壛巳肿兞俊?br />
1. 狀態(tài)模式
對(duì)象行為的行為依賴于它所處的狀態(tài),對(duì)象的行為隨著狀態(tài)的改變而改變。解決不同狀態(tài)下,行為不同的問(wèn)題。
問(wèn)題:
左邊樹(shù)狀結(jié)構(gòu),選擇不同節(jié)點(diǎn),右邊Viewer顯示該節(jié)點(diǎn)下圖片;左邊樹(shù)下方search框,右邊Viewer顯示滿足search條件的圖片。抽象出Viewer對(duì)象,有兩個(gè)不同狀態(tài)view和search,不同狀態(tài)下更新Viewer的方式不同,即
tree.onselect -> state="view"
search -> state="search"
if(state="view"){
updateView(path,start,limit)
}else if(state="search"){
updateSearch(path,start,limit,searchCriteria)
}
Viewer, search, tree耦合在一起,需要全局變量state。
解決方案:
抽象兩個(gè)狀態(tài)對(duì)象
viewAction -> updateView(path,start,limit)
searchAction -> updateSearch(path,start,limit,searchCriteria)
Viewer對(duì)象
變量 updateAction
方法 setUpdateAction(action)
方法 update()調(diào)用 -> updateAction()
狀態(tài)改變時(shí),改變所選的狀態(tài)對(duì)象
tree.onselect -> Viewer.setUpdateAction(viewAction)
search -> Viewer.setUpdateAction(searchAction)
Viewer, search, tree解藕,去除了全局變量state,行為局域化。假如以后加入view,search外的其他狀態(tài),只需增加newAction狀態(tài)對(duì)象,并在調(diào)用處添加Viewer.setUpdateAction(newAction),便于擴(kuò)展,無(wú)需改變現(xiàn)有代碼。
2. 不知道該叫什么模式
問(wèn)題:右鍵事件
if(action=="addTag"){
addTag()
}
if(action=="replaceTag"){
replaceTag()
}
if(action=="addSubjectTag"){
addSubjectTag()
}
if(action=="addCredit"){
addCredit()
}
增加新事件需要添加新的if語(yǔ)句
--------------------------->
中間過(guò)程
var items={
"addTag":addTag,
"replaceTag":replaceTag,
"addSubjectTag":addSubjectTag,
"addCredit":addCredit
}
perform(){
items[action]()
}
--------------------------->
事件注冊(cè),提供注冊(cè)接口
var items = {}
perform(){
items[action]()
}
register(option){
items.add(option)
}
增加右鍵事件時(shí),只需自行注冊(cè),事件的執(zhí)行與事件本身完全解藕,同時(shí)新事件加入時(shí),只需注冊(cè),無(wú)需改變現(xiàn)有代碼。
regsiter({"addTag":addTag})
regsiter({"replaceTag":replaceTag})
regsiter({"addSubjectTag":addSubjectTag})
regsiter({"addCredit":addCredit})
REST顧名思義:Representational State Transfer(表述化狀態(tài)轉(zhuǎn)移)。 REST兩個(gè)基本概念:
- 資源(Resource):將信息抽象為資源,任何能夠命名的信息(包括數(shù)據(jù)和功能)都能作為一個(gè)資源,一張圖片,一份文檔,一個(gè)服務(wù)(如上傳圖片),一個(gè)其他資源的集合等。
資源是到一組實(shí)體的概念上的映射,而不是在特定時(shí)刻與該映射相關(guān)聯(lián)的實(shí)體的映射。例如,“最新上傳的圖片”是一個(gè)值經(jīng)常變化的映射,但是“2011/1/9上傳的圖片”的映射是靜態(tài)。它們是截然不同的資源,即使某一時(shí)刻它們可能會(huì)映射到相同值的集合。
- 表述(Representation): 一個(gè)資源當(dāng)前或預(yù)期的狀態(tài),資源是一個(gè)隨時(shí)間變化的函數(shù),該函數(shù)將時(shí)間t映射到一個(gè)實(shí)體或值的集合,集合中的值可能是資源的表述。REST組件通過(guò)URI來(lái)獲得資源的表述并對(duì)資源執(zhí)行動(dòng)作,并在組件間傳遞該表述。
舉購(gòu)物網(wǎng)站系統(tǒng)的例子,products通過(guò)加入購(gòu)物車到orders,經(jīng)過(guò)付款訂單到purchase,然后到delivery。其中products和orders是資源,可以通過(guò)/products?color=green和/orders/2007/11表示;而purchase和delivery是資源products和orders某一時(shí)間的狀態(tài)。應(yīng)用程序如同資源和表述組成的虛擬的狀態(tài)機(jī),通過(guò)不斷的獲取資源的表述來(lái)轉(zhuǎn)變應(yīng)用程序的狀態(tài),即所謂的表述化狀態(tài)轉(zhuǎn)移。
REST風(fēng)格架構(gòu)的約束:
- 客戶-服務(wù)器:分離關(guān)注點(diǎn),將用戶接口(如用戶界面)和數(shù)據(jù)存儲(chǔ)分離,如果接口不變,組件可獨(dú)立進(jìn)化。
- 無(wú) 狀態(tài):從客戶端道服務(wù)器的每個(gè)請(qǐng)求必須包含理解該請(qǐng)求所必需的所有信息,不能利用任何存儲(chǔ)在服務(wù)器上的上下文。提高了系統(tǒng)的可擴(kuò)展性,其優(yōu)點(diǎn)有三:可見(jiàn)
性,監(jiān)視系統(tǒng)不必為了確定一個(gè)請(qǐng)求的性質(zhì)而去查看請(qǐng)求之外的多個(gè)請(qǐng)求;可靠性,減輕了從局部故障恢復(fù)的任務(wù)量,可以快速定位;可伸縮性,不必在多個(gè)請(qǐng)求之 間保存狀態(tài),允許服務(wù)器快速釋放資源,并且服務(wù)器不必跨請(qǐng)求管理資源。缺點(diǎn)是,由于不能將狀態(tài)保存在服務(wù)器上的共享上下文中,增加了請(qǐng)求中發(fā)送的重復(fù)數(shù)
據(jù),降低網(wǎng)絡(luò)性能,因此有了約束三。
- 緩存:請(qǐng)求響應(yīng)中的數(shù)據(jù)顯示或隱式的標(biāo)記為可緩存的不可緩存的。緩存可以為以后相同的請(qǐng)求重用這個(gè)響應(yīng)的數(shù)據(jù)。但是緩存管理不好,會(huì)降低可靠性,導(dǎo)致緩存中陳舊的數(shù)據(jù)與直接訪問(wèn)服務(wù)器得到的數(shù)據(jù)差別很大。
- 統(tǒng)一接口:組件之間要有統(tǒng)一的接口,是REST風(fēng)格架構(gòu)區(qū)別其他風(fēng)格架構(gòu)的核心特征。REST由四個(gè)接口約束定義:資源的識(shí)別,web-based系統(tǒng)中資源由URI表示,數(shù)據(jù)庫(kù)存儲(chǔ)系統(tǒng)中資源還可以是XML,JSON等;通過(guò)表述對(duì)資源執(zhí)行的動(dòng)作:表述中包含操作該資源的信息,如增刪改查,映射到HTTP協(xié)議的GET,POST,PUT,DELETE方法;自描述的消息:消息中包含如何處理該消息的信息,如哪個(gè)sevlet處理,響應(yīng)中包含可不可以被緩存等信息;作為應(yīng)用狀態(tài)引擎的超媒體。
- 分層系統(tǒng):通過(guò)限制組件的行為,即每個(gè)組件只能看到與其交互的緊鄰層,將架構(gòu)分解為若干等級(jí)層,提高各層的獨(dú)立性。
- 按需代碼:客戶端知道如何訪問(wèn)資源,但是不知道如何處理它,向服務(wù)器發(fā)送對(duì)于如何處理資源的代碼的請(qǐng)求,接收這些代碼,然后在本地執(zhí)行代碼。
Groovy與Java同是運(yùn)行在JVM之上,大多數(shù)時(shí)候可以把Groovy當(dāng)做Java來(lái)用,但是也存在著陷阱。
例如,想重寫(xiě)對(duì)象的equals方法,需如下步驟:
1. 使用==判斷參數(shù)和自身對(duì)象引用是否相等,如果相等直接返回true(此判斷可以提高性能);
2. 使用instanceof判斷參數(shù)類型是否正確,如果不正確直接返回false
3. 將參數(shù)轉(zhuǎn)換為正確的類型,步驟2已確保了類型
4. 檢查參數(shù)與自身對(duì)象關(guān)鍵字段是否相等
1)基本類型(除float和double)運(yùn)用==比較
2)對(duì)象引用,遞歸調(diào)用equals方法
3)浮點(diǎn)型float,調(diào)用Float.floatToIntBits轉(zhuǎn)化為int,用==比較
4)浮點(diǎn)型double,調(diào)用Double.doubleToLongBits轉(zhuǎn)化為long,用==比較
遵循以上步驟,重寫(xiě)簡(jiǎn)單Money類的equals方法:
1 public boolean equals(Object another) {
2 if (another == this) return true
3 if (another instanceof Money) {
4 Money anotherMoney = (Money) another
5 return amount == anotherMoney.amount
6 }
7 return false
8 }
調(diào)用
1 new Money(10).equals(new Money(10))
得到異常
java.lang.StackOverflowError
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1049)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:880)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:746)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:729)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareEqual(DefaultTypeTransformation.java:620)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareEqual(ScriptBytecodeAdapter.java:680)
at Money.equals(Money.groovy:10)
最終發(fā)現(xiàn)罪魁禍?zhǔn)资?#8220;==”,Groovy中對(duì)于所有類型(基本類型和引用)調(diào)用“==”和“equals()”方法相同,當(dāng)執(zhí)行“
another == this”時(shí),解釋器會(huì)調(diào)用“
another.equals(this)”造成死循環(huán)。
如果想比較引用相等,Groovy提供了is方法,即
1 public boolean equals(Object another) {
2 if (another.is(this)) return true
3 if (another instanceof Money) {
4 Money anotherMoney = (Money) another
5 return amount == anotherMoney.amount
6 }
7 return false
8 }