Shooper.Java

          Beginning Java

          給JAVA設計開發新手的一些建議和意見

          作者:飛云小俠????來自:CSDN

            為了給朋友同事一些設計問題上的指導,特撰寫此文,很多觀點都是從別人的文章中獲取,有些觀點肯定也有偏頗,有些觀點也僅僅是提出并沒有做詳細論述,請多拍磚,以便改正。

            【概述

            在工作中,作為一個程序員或者一個設計師,總是要設計一些函數庫或者一個框架,當然最經常的還是做項目,即使是一個項目,也會被經常改動,甚至交給別人改動。
            當你做這些工作的時候,你的這些成果都是要給別人了解使用的,或者說給以后的你使用的,為了別人的方便或者為了自己的方便,我們要盡可能做好設計。
            

            【放正心態,任何東西都是不斷發展的

            技術是日新月異的,每一天都有新的技術出來,正所謂"山外有山,人外有人",每一個新的輪子出來,都可能比你要設計的輪子好,所以在設計的時候,應該了解一下是否已經有了類似的輪子,是否要設計一個新的輪子。

            即使你的輪子已經設計好了,也不好認為自己的輪子一定比別人的輪子好,雖然你的輪子可能更適合你的實際使用。

            技術在不斷的發展中,你以及你的朋友/同事都在不斷進步,"士別三日,當刮目相看",所以不要認為你的水平一定比別人高,"尺有所短,寸有所長",所以別人對你的函數庫/框架提出意見,提出疑問的時候,請不要驚奇,不要反感,不要認為別人在"挑刺",也許你的函數庫/框架早就不適合當前的發展了。
            
            態度決定一切。你的領導或許更重視這一點。
            
            【必要的組成部分:單元測試,文檔,實例,手冊etc

            單元測試,文檔,API Doc,手冊,演示程序,Change Log,Readme,build。xml等等

            有一天別人使用了你設計的函數庫/框架,當你升級后,原來的項目卻不能工作了,經過一天的調試,你終于找到了原因,原來是不小心寫錯了一個東西。

            你肯定不希望上述的事情發生,那么請你寫單元測試吧,這樣既不浪費自己的時間,也不耽誤別人的工作,何樂而不為。你花在寫單元測試的時間/帶來的樂趣和你升級后改正莫名其妙的錯誤的時間和苦惱相比,肯定更有價值。你看到單元測試的綠條,難道不感到高興嗎?!

            如果你不能保證你的程序修改沒有錯誤,不要指望你的同事認為你的錯誤是可以容忍的,他們在心里早就開始罵你了,呵呵。寫單元測試吧
            
            看看任何一個知名的框架,都包含完善的文檔,單元測試,示例程序,用戶手冊,那么請你也包含這些吧。哦,對了,請詳細地寫好JavaDoc,它很重要。
            
            使用你的框架/函數庫的人如果到處去找使用方法,去找某個類(但是他不知道是否有這個類),那么說明你的文檔沒有到位。如果你希望別人使用你的這個類或者功能,那么請寫好文檔,不要指望別人去讀你的源碼然后就能理解它是干什么用的。
            
            如果你做到這些,那么你的函數庫/框架也有了"知名"的前提,難道不是嗎?如果沒有,我想是沒法讓別人更好地使用的。
            
            對了,有了這些東西,還要有一個良好的目錄組織,這個也可以參考別的框架的組織方式。

           【借鑒成熟的設計,參考已有的項目

            1. 要做一個新的東西,沒有想法。不要驚訝,我肯定先找一個現有的東西來借鑒。
            
            當然前提是不要重新發明輪子,或者是你有充分條件要重新發明一個輪子。
            Struts,WebWork,Spring等等都是成熟的框架,不管你使用起來是否符合你的習慣。
            在你成為大師之前,你的設計思想估計前人都已經提出并實踐過了,所以要勇敢地去借鑒。"站在巨人的肩膀上"我們能更近一步。
            
            例如我們厭倦了在訪問數據庫時使用如下的代碼:

            try
            {
            //your code here
            }
            catch(Exception e)
            {
            //catch Exception
            }
            finally
            {
            //must do something
            }

            我們就可以借鑒Spring框架的JdbcTemplate類,看看它是如何利用回調函數來處理的。
            
            我們使用hibernate時是不是也會使用類似上面的代碼,那么可以參考Spring框架的HibernateTemplate。
            
            借鑒也是一種捷徑。
            
            警告:借鑒但不要抄襲,借鑒代碼要注明來源,尊重他人也是尊重自己。
            
            2. 在實際的項目中,往往可以參考已經有的項目來做自己的設計。
            
            例如做一個網站,我不知道如何訪問數據庫,如何布局,如何分層,那么我們可以參考已經有的網站程序,看看別人是如何利用SiteMesh或者tiles布局,如何使用Hibernate來訪問數據庫或者使用已經封裝好的JDBC類來訪問數據庫,如何利用Struts,WebWork或者其他訪問來分層。

            【遵守約定俗成的一些做法

            為了使別人更方便地使用你的東西,那么在設計一些通用的函數或者類的時候,請遵守通用的做法,不要與眾不同,除非你的內部實現確實與眾不同。

            例如實現一個類似ArrayList的類,那么請不要這樣寫:

            public int count()
            {
            return list.size();
            }
            public Item getItem(int i)
            {
            return list.get(i);
            }
            
            而應該這樣:

            public int size()
            {
            return list.size();
            }
            public Item get(int i)
            {
            return list.get(i);
            }
            
            當然每個人都有自己的想法,如果你非常認為你原來的方式比普通的好,那么請提供2套方式供別人選擇。它不會給你帶來麻煩,只是一個一看就懂的做法,不用懷疑,這樣做有好處。
            
            很多類的設計都有一些約定俗成的做法,那么在你設計一個新類的時候,先借鑒一下吧,多看看JDK的源碼/文檔,看看別人是怎么實現的。這更有助于推廣你的成果。
              
            【不要迷信權威

            在使用已有的框架或者函數庫時,不要認為所有的東西都是正確的或者是最好的最好,肯定不是。沒有完美的東西,已經存在的東西在設計的時候因為種種局限或者因為作者的水平,對現在來說肯定存在不合理的設計,或者過于理想化的設計,而不能滿足實際情況。
            
            不迷信權威,才能到達新的境界。

            【不要輕易排斥,不了解就不要草率發表意見,要嚴謹

            在網上經常看到。Net和Java的比較/火拼,或者是Struts VS Webwork或者是其他等等,非常之多。經常看到的是一方對對方的東西不甚了解,就開始批評,結果說不到點子上,反而被嘲笑一番。
            幾種技術的比較有時候是必要的,例如技術選型的時候。但是如果一些對這些技術根本不了解的人來選型,來評判,你能對結果信服嗎?
            存在就是合理,任何技術都有其存在的理由,雖然有些東西早就過時了,但是在當時它也是應運而生的。
            幾種技術,都是來解決同樣的問題,但是問題也有很多方面,解決方式也有很多種,每個人的想法也都不一樣,思路也不一樣,所以沒有絕對符合要求的技術,但是應該有符合你的技術,不符合你的技術不等于也不滿足別人的要求。所以不要輕易排斥別的東西。
            
            在做技術比較的時候,如果你不了解,那么請不要輕易發表意見,至少你可以親自去了解,去實踐之后在發表你的意見豈不是更好。
            
            在發表意見的時候,也要嚴謹,不要輕易下結論,要經過求證,否則一旦錯誤只會讓對手笑話,讓你的同事看不起你。例如你說Hibernate3不支持jdk1。3,那么最好去好好找到你的證據,否則就會成為錯誤。(Hibernate3支持jdk1。3)
            
            作為一個技術人員,嚴謹應該是我們的習慣之一,無論做開發還是做設計。

            【處理好你的異常

            異常處理是Java編程中非常重要的一個部分。建議在使用異常之前閱讀或者。
            
            下面從書中摘出幾條建議:
            * 絕對不要忽略異常
            * 千萬不要隱藏異常
            * 僅在不正常的情況下使用異常
            * 對可恢復的情況使用可檢查異常,對程序錯誤使用運行時異常(RunTimeException)
            * 給方法引發的異常做文檔
            * 在詳細信息里面包括失敗捕獲信息
            * 使用finally避免資源泄漏
            * ....
            
            在這里特別提出的是,在開發中要特別處理NULL的情況,否則經常引發NullPointException異常,在Java里這是一個最令人頭疼的異常了。
            如果你的程序因為一個NULL值,而報了幾十個NullPointException的話,不但得讓人煩死,而且還非常難以找到錯誤所在。所以在Java中一定要注意這個問題。
            如果你的函數不允許Null值,那么可以截獲它,拋出一個異常,或者給客戶更友好的提示,難道不好嗎?
            
            讓我們來看一個例子:

            public String getName(User aUser)
            {
            //如果aUser為Null,會發生什么情況
            return aUser.getName();
            }
            
            很明顯,如果參數為Null,就會拋出異常。應該改為:
            public String getName(User aUser)
            {
            if(null=aUser)
            {
            return "";
            }
            else
            {
            return aUser.getName();
            }
            }
            
            或者你要求參數不能為空,還可以拋出一個異常,強制使用者不能傳入空值。
            
            還有經常被忽略的是RunTimeException和普通異常的區別,在Java中,這是一個特殊的異常類,程序中如果遇到這個異常,用戶可以不截獲它,而如果是其他的普通異常,就不許要截獲它。我們的代碼經常這么寫:
            try
            {
            //your code here
            }
            catch(Exception e)
            {
            //do warn
            }

            這樣寫的話,就截獲了所有異常,當然也包括了RunTimeException。 在很多情況下,這是不合適的處理方式,我們只應截獲必要的異常,而應該忽略RuntimeException。
            
            關于RunTimeException,在Spring中還有更好的利用方式,建議閱讀Spring框架中在事務中對異常的處理代碼,例如對Jdbc拋出的SqlException的轉換。
            
            關于異常處理,我提出幾點建議:
            * 捕獲異常而且再次拋出時要包含原來的異常信息
            * 不要忘了RunTimeException,除非必要,否則不要用catch(Exception e)的方式捕獲所有異常。
            * 不要用異常做流程控制,異常的性能代價比較高昂。(對此,可能有人不同意。此處不詳細討論)
            * 不要把異常處理都拋給別人,本函數有能力處理的就不要拋出。
            
            在此建議讀者詳細閱讀或者。
            
            【過度依賴

            在定位錯誤的時候,經常遇到瀏覽了七 八個文件還是沒有找到什么地方執行了真正需要的函數,這個時候就非常郁悶。A調用了B,B調用了C,C調用了D。。。。。。讓人找不到北
            
            面對這樣的程序,存在的問題不僅僅是定位錯誤麻煩,而且如果需要維護這樣的函數庫/框架,恐怕你的有非常高的統御能力才行,否則打死我也不去維護。
            
            那么我們自己最好不要寫這樣的程序出來給人用。

            【濫用接口

            現在流行"面對接口編程",這本身本來是不錯,但是濫用接口的現象卻經常發生。
            "面向接口",于是所有的類都有一個對應的接口,接口的函數聲明和類一模一樣,而且一個接口只有一個類來實現它。這樣的面向接口有什么意義哪? (為了用Spring的事務的情況除外)
            
            根據"迪比特法則(Law of Demter)",一個對象應當對其他對象有盡可能少的了解。一個接口內應該只定義對方所需要的方法,而不要把一些沒用的方法聲明放在接口里面。
            
            例如如下一個類:
            
            public class MyCounter
            {
            private int n1;
            private int n2;
            public MyCounter(int n1,int n2)
            {
            this。n1=n1;
            this。n2=n2;
            }
            
            public void setN1(int n1)
            {
            return this.n1 = n1;
            }
            public void setN2(int n2)
            {
            return this.n2 = n2;
            }
            public int getN1()
            {
            return n1;
            }
            public int getN2()
            {
            return n2;
            }
            
            public int getResult()
            {
            return n1 + n2;
            }
            }

            我們可以看到,這個類的主要目的是得到計算結果,所以正確的接口應該類似:
              
            public interface Counter
            {
            int getResult();
            }
            
            但是很多情況下,經常是這樣的接口:
              
            public interface Counter
            {
            int getResult();
            int getN1();
            int getN2();
            void setN1(int n1);
            void setN2(int n2);
            }
              
            我們想一想,這樣做有2個后果:
            1. 除了getResult之外,其他的函數我們根本用不到,所以是多余的。
            2. 如果我們要自己實現一個Counter,如果接口中僅僅定義了getResult,我們僅僅需要實現它就可以了。我們自己的類可能是多個數運算,有乘除加減等等各種運算,參數也有可能是一些數組。但是如果按照第二種方法聲明接口的話,我們就必須實現后面的四個方法,如果這樣的話,實現這樣東西不僅沒用,而且浪費時間。我們恐怕要大聲罵娘了吧。
            
            所以,接口有好的作用,但是不要濫用。
            ■ 如果你的接口永遠只有一個類實現,那么可能就沒有必要用接口。
            ■ 你的接口只需要聲明別人用到的函數即可。

            【空接口的使用】

            在接口使用的時候,空接口有2種情況:
            1. 類似Cloneable,Serializable,他們往往是做一個標記,表示需要某個功能。當然你也可以這么用,來表示你的類具有某個功能,實現了你的某個接口。
            2. 你的接口繼承了別的接口(非空),你的接口本身沒有聲明函數。這種情況一般是你不希望用戶使用父接口來作為參數類型,因為他們的用途可能不同,此時就可以用空接口來實現。
            
            第一種情況我們不再多說,搜索一下關于Cloneable,Serializable的文章就會了解很多。
            我們來看下面的代碼:

            public interface Text
            {
            String getText();
            }
            
            public interface SqlText extends Text
            {
            }

            可以看到,Text接口是用于返回一個字符串。而SqlText是一個空接口,它繼承了Text接口。也就是說SqlText也是一種Text。但是我們可以知道,任何一個字符串不一定是Sql字符串,所以此時聲明了一個SqlText接口來用于表名當前的字符串是一個Sql字符串。你的函數可以這樣聲明:

            public void doQuery(SqlText aSqlText)

            而不是這樣
            
            public void doQuery(Text aText)

            避免用戶產生歧義的想法,一眼看去,就明白應該傳入一個Sql字符串。
            
            【繼承層次過多】
            一般來說,繼承的層次不要過多,否則使用者可能會討厭,找一個函數會很麻煩。很多Java語言檢查工具都建議你的繼承層次不要超過3層。
            
            【Has A ,Is A,不要濫用繼承】

            "我是一個Mp3","我有一個Mp3",其實很容易分辨。但是在實際應用中,往往存在把"我有一個Mp3"的情況當作"我是一個Mp3",或者是為了偷懶方便而放松了對自己的要求,甚至還沾沾自喜,感覺找到一個捷徑。(scud以前也干過這種事情)。
            
            以前我曾經這樣干過:我的邏輯類直接繼承了我的數據庫訪問類,這樣我可以直接在邏輯類里面訪問:
            
            public MyLogic extends MyDBA
            
            aLogic.getInt("click");
            aLogic.getString("name");
            
            看起來是非常方便,但是你的邏輯類就牢牢綁在了DBA上,是一種非常不好的做法。現在我這樣聲明:

            public MyLogic
            
            MyDBA adba;
            
            adba.getInt("click");
            adba.getString("name");

            其實代碼改動不大,但是你的邏輯類不在牢牢綁在DBA身上了,何樂而不為。
            
            其實這種現象在開發人員中間可能經常見到,我們要盡量避免。下面再來看一個例子:
            
            //一個保存分頁信息的類
            
            public class PageInfo
            {
            private int page;
            private int pageCount;
            private int recPerPage;
            private int recCount;
            
            //get,set method list...
            }

            一般的情況是,在Dao中進行分頁查詢,計算總記錄,總頁數等等,所以需要把PageInfo傳給Dao。而在邏輯類中,把傳回來的分頁信息數據推到FormBean或者是Action中。
            也許你會這么想,如果我的Action或者FormBean繼承了PageInfo,豈不是要省很多事。
            
            千萬別這么干。并不是所有的動作都需要分頁信息,你的FormBean和PageInfo沒有繼承的關系。也就是說FormBean Has A PageInfo,但是不是Is A PageInfo。
            
            【保持外觀/行為一致】

            外觀一致其實很容易理解,例如你用size()表示得到一個List的大小,那么在所有的List類中你都用size()得到它的大小,這就是外觀一致。
            外觀一致讓用戶更方便使用你的函數庫,不用記住幾個不同的表示同一個功能的函數名字。或者幾個名字相同功能卻不同的函數。那就很糟糕了。
            
            行為一致相對外觀一致就相對比較難做到,但是優秀的設計師肯定會讓他的成果行為一致,而不是出人意料的行為,也不是一套強行規定的行為。
            
            我們來看下面的代碼:
            
            import java.util.HashMap;
            import java.util.Map;

            class UserInfo
            {
            private String realname;
            
            public UserInfo(String sName)
            {
            this.realname = sName;
            }
            
            public void setName(String sName)
            {
            this.realname = sName;
            }
            public String getName()
            {
            return this.realname;
            }
            }
            
            public class MyTest
            {
            
            Map userInfoMap = new HashMap();
            
            public void setUserInfo(String sName,UserInfo aInfo)
            {
            userInfoMap.put(sName,aInfo);
            
            userInfoMap.put(aInfo.getName(),aInfo);
            }
            
            public UserInfo getUserInfo(String sName)
            {
            return (UserInfo)userInfoMap.get(sName);
            }
            
            public static void main(String args[])
            {
            MyTest aTest = new MyTest();
            
            UserInfo aUserInfo = new UserInfo("王小二");
            
            aTest.setUserInfo("兒童團團長",aUserInfo);
            aTest.setUserInfo("三班班長",aUserInfo);
            
            UserInfo 兒童團團長 = aTest.getUserInfo("兒童團團長");
            
            if(null!=兒童團團長)
            {
            System.out.println(兒童團團長.getName());
            }
            else
            {
            System.out.println("兒童團團長 Not Found");
            }
            
            UserInfo 王小二 = aTest.getUserInfo("王小二");
            
            if(null!=王小二)
            {
            System.out.println(王小二.getName());
            }
            else
            {
            System.out.println("王小二 Not Found");
            }
            
            }
            }
            可以看到,上面的代碼運行結果是"王小二",也就是說兒童團團長是王小二,王小二本身也是王小二,這一切正常。
            
            現在我們把setUserInfo里面的第一句注釋掉:
            
            public void setUserInfo(String sName,UserInfo aInfo)
            {
            //userInfoMap.put(sName,aInfo);
            
            userInfoMap.put(aInfo.getName(),aInfo);
            }

            再次運行上面的代碼,我們發現兒童團團長不存在了,但是王小二還在。還可以看出,如果找"三班班長"的話,肯定也找不到,也就是說只有依據王小二的真名才能找到王小二,其他方法就不行了。
            
            從上面的setUserInfo和getUserInfo分析,如果采用修改后的代碼,我們的程序就出現了行為表現不一致,而這是令人迷惑不解的,我們set了半天,卻找不到,豈不是令人惱火!
            
            當然上面的代碼比較簡單,通過簡單的修改就能做到行為一致,但在實際編程中,往往因為復雜的行為操作,經常會造成行為不一致,從而給開發人員帶來困惑。

            【MVC,MVC2,WEB設計編程的分層】

            請閱讀文章 http://forum.javaeye.com/viewtopic.php?t=11712&postdays=0&postorder=asc&start=0

            【可擴展不等于功能強大,不要夸大其辭】

            現在的系統,因為接口或者其他方法的使用,都具有很大的擴展性。但是擴展性不等于功能強大。
            存在一個接口,用戶可以實現自己的接口,確實非常方便。但是如果你的系統本身只實現了一個接口或者根本沒有實現,那么對用戶來說就談不上方便。
            
            例如WebWork的validators,本身是一個接口,但是實際上本身實現的具體類很少,而且功能很差,這個時候如果你說WebWork的校驗器很厲害,那么就可能不太恰當了。當然擴展Webwork的Validator還是非常方便的。
            
            當然,可擴展性還是需要的,但是不要吹噓,在這個浮躁的年代,讓我們多干點實事。 :)

            【20/80原則】
            
            在工作中,我經常想到20/80原則,也就是"巴雷多原則"。例如我們可以看到:

            時間:我們20%的時間會產生成果的80%
            
            產品:產品的20%帶來利潤的80%
            
            閱讀:20%的書篇幅包括了內容的80%
            
            工作:20%的工作給我們80%的滿意
            
            演講:20%的演講產生影響的80%
            
            領導:20%的人作出80%的決定

            從上面可以看出,很多時候它都很有說服力。
            在這里我想提到幾點,但是和上面的可能出發點有所不同:
            
            1、程序的80%都是在處理特殊情況,所以我們一定要對特殊情況重視,不要因為是特殊情況,就不很重視。80%的客戶對特殊情況都很重視。
            文檔對特殊情況也要詳細描述,因為開發人員80%的時候在查找這些東西,而對那些經常用到的用法卻很少查閱文檔。
            
            2、優化問題:80%的瓶頸都出在20%的代碼上,所以在優化代碼的時候不需要優化所有代碼,只需要優化20%的關鍵代碼就夠了。當然追求完美的人我們就不多說了。
            記得有一條優化的原則是"不要優化!不要優化",是非常有道理的。
            
            3、如果你20%的事情做砸了,往往會導致80%的事情都砸了,或者是導致別人認為你把事情幾乎都做砸了。
            如果你對一些事情發表了一些很不嚴謹的看法,那么別人會認為你在別的事情上也很不嚴謹。
            依此類推,代碼質量,文檔完整性等等,都會讓人產生類似的推理。
            
            (當然一個代碼寫的很亂的人,往往文檔也很亂。)
            
            【強制綁定是不受歡迎的】

            不要在程序中強制綁定一些額外的功能。
            
            有的框架往往功能很多,是"大型計算機",有很多功能,但是在我需要打字的時候,給我打字的功能即可,不要強制我使用網絡功能,打印功能,負載均衡功能等等。
            
            一般來說,如果一個東西有很多功能,那么做好做成可配置,可插拔的,這樣用戶使用你的東西,沒必要在不使用高級功能的時候,浪費用戶的內存,磁盤。開發人員還得多copy好多lib文件,占用調試時間,豈不是很麻煩。
            
            不要買一送一,我不想要就別給我。 :)

            【有時候也得考慮兼容性】

            一般來說,一個公司的客戶會有很多,用戶的運行環境是各種各樣的。jdk1.3,jdk1.4甚至還有jdk1.2。這樣我們在編程的時候就必須做一些妥協,有些函數庫就不能使用。
            如果這些用戶的jdk不能升級(一般來說都需要購買新的產品才能升級),或者我們必須對這些情況妥協,那么我們就要在開發中考慮這些問題。
            
            例如以前,在Servlet 2.2的時候,因為沒有setCharacterEncoding,我們必須手動對各種字符進行轉換。當Servlet2.3的時候,可以使用這個函數了。但是為了客戶考慮,我們只好沒有升級還是使用原來的方法。(當然后來大多數用戶都使用了新的App Server,我們就可以使用filter來處理編碼問題了)。
            
            向下兼容性確實讓人頭疼,JDK1.5也發布好久了,不過我們現在也不能使用,只能自己沒事測試測試。
            
            在編程的時候,一定要設置好IDE的兼容性設置,防止我們使用了不能使用的特性。Jbuilder,Eclipse都有類似的設置。
            
            【成本與現實,給用戶以選擇余地】

            全文檢索,lucene,like是三種對大文本字段檢索的方法。那么你采用哪一種呢?
            
            也許你會毫不猶豫的說"全文檢索" (我看你像TRS公司的托 :P)。
            
            正如"強制綁定是不受歡迎的"里面所說的一樣,我還是覺得應該給用戶以選擇的余地。
            
            全文檢索是要花錢的或者需要配置,而且一般來說數據庫專用的全文檢索都是不通用的,lucene是需要開發人員開發的,只有like最簡單了,但是太簡單了,而且性能也差。
            
            這個時候,也許我們就應該提供幾種方式供用戶選擇了,用戶如何選擇那就看他們了。。。
            
            【結束語】

            實際開發設計中肯定還存在很多其他的問題,本文不可能一一論述。到此為止。 :)
            
            希望各位在開發設計中成為高水平的設計師。 :)

          posted on 2006-05-09 23:57 Shooper.Java 閱讀(297) 評論(0)  編輯  收藏 所屬分類: 程序心得體會

          主站蜘蛛池模板: 会同县| 高安市| 保定市| 常宁市| 海兴县| 中山市| 互助| 海城市| 临邑县| 临泽县| 威宁| 黔东| 三穗县| 昌宁县| 高陵县| 元谋县| 哈巴河县| 新绛县| 合水县| 香格里拉县| 蓝田县| 宝丰县| 承德县| 达日县| 黄山市| 安国市| 新田县| 八宿县| 阳城县| 张掖市| 晋城| 福州市| 晴隆县| 西城区| 肇源县| 胶南市| 义乌市| 福鼎市| 凤阳县| 南汇区| 柏乡县|