1.背景
對于一般性的地圖顯示需求,我們只需要知道地圖的一個固定URL,然后知道要顯示的范圍和要顯示的級別以及每個級別的scale等即可。
但是如果我們遇到下面幾種情況時,又該如何。
(1)需要顯示的圖層的不同級別來自于不同的服務器(URL不同):如前幾個級別由一個固定URL提供,后幾個級別由另外一個URL提供。
(2)需要同時疊加幾張圖層:如需要疊加地形圖和注記圖,且圖層的顯示有層級之分。
(3)需要同時疊加幾張圖層,并且每個圖層初始顯示級別不一樣。
(4)需要同時疊加幾張圖層,圖層的URL的請求方式均有不同:如其中一個圖層由AGS提供,一個圖層是天地圖服務提供,還有一個圖層是當地城管局提供,最后還有一個圖層是由本地未發布的緩存瓦片提供。
諸如類似于以上的地圖顯示需求還有很多,如果我們單純的對每一種情況進行一個代碼上的分支當然也是能解決的。但是這并不是最好的一種解決方法,每次新的需求提出時,都需要對代碼修改,這不符合設計模式中的開放封閉原則。雖然我們可以用簡單工廠等模式來努力改善這種情況,不過如果有更好的方式,不需要修改任何代碼,不需要用設計模式,就能解決以上的問題是否更好?
下面我將給出一種通過
數據庫配置來實現上面所有問題的解決方案。此方案在多個項目中已經開始使用。
2.配置(表)的設計
2.1原理
首先我們必須對以上多種地圖顯示的需求進行一個分析,提出他們的共同點。
(1)對圖層開始顯示的級別有需求(startLevel)。
(2)多張圖層疊加,并且圖層疊加有從上自下的順序(layerDisplayOrder)。
(3)圖層可能的URL不同(ServiceURL)
(4)每個圖層的URL格式可以不一樣,比如URL可能天地圖的WMTS格式,可能是AGS發布的請求方式(level\row\col),也有可能是Geoserver發布的WMS格式。并且有的服務提供商還需要token字段來判斷是否有權限得到服務,或者不同的服務商提供的WMTS格式中對col和row以及level的表述字段名稱不一樣(通過這個分析,可以提煉出Xfield、Yfield、LevelFieldName、Token字段來對不同的需求進行配置)。
(5)所有的圖層,如果要疊加,需要用同一個空間參考,同一個瓦片大小,同一個地圖起始原點,以及同一套地圖比例尺。
(6)變化的只是URL,其核心瓦片行列號和地圖級別是每種瓦片請求URL均需要的。
2.2設計
2.2.1圖層列表(tcMaplayerList)的設計
首先我給出圖層列表設計的截圖:
(1)每一個圖層均有一個圖層名
(2)每一個圖層均有自己的圖層類型,比如AGS類型的、WMTS類型的等。這是為了程序中對不同的類型的URL進行解析。
(3)每一個圖層有其自己的顯示順序,為了正確的疊加圖層之用。
(4)每一個圖層也有自己開始顯示的級別。如有的圖層想第一級別開始顯示,有的圖層希望地圖放大到第二級別時才開始顯示。
2.2.2圖層詳細內容列表(tcgismapservicedetail)的設計
同樣,這里先給出表的截圖:
(1)ItemID為每個記錄的主碼。
(2)layerName與tcMaplayerList中的圖層名是對應的。
(3)圖層級別表示的是該圖層在此級別時的信息。
(4)圖層在該級別的URL有一個固定的部分,比如WMTS請求中,變化的只是行列號,而前面的部分均是不定的。
(5)Token、XFieldName、YFieldName、LevelFieldName均是為擴展之用,當URL需要給行列號以及級別一個固定的名稱時等,則配置。否則不用。
3.工作流程
3.1流程圖
3.2流程詳解
3.2.1得到需要顯示的圖層列表
在顯示地圖之前,需要先向后臺發出請求,此請求的參數主要是layerType,后臺根據layerType將tcMapLayerList表中符合需求的內容讀出返回。
3.2.2 解析當前地圖級別下的各個圖層信息,并順序顯示
在解析了需要顯示的圖層列表后,每個圖層均會給后臺發出自己的信息,信息中包括了此時地圖的級別,以及需要得到的瓦片行列號和自己的圖層名。
但是此時的地圖級別并不是圖層發給后臺的級別參數,真是的地圖級別是:
factLayerLevel=layerLevel+startLayerLevel。
根據factlayerLevel和layerName,在tcgismapservicedetail中找到對應的信息,如果有信息,則表示該圖層在此真實級別下是需要顯示的,然后根據該圖層的layerType將此時的URL拼接出來,下載瓦片然后返回此瓦片。
如果沒有查到數據則不顯示此地圖級別下的該圖層。
4.成果展示
4.1測繪局地圖+本地瓦片
4.2 天地圖地形圖層+天地圖注記圖層+Geoserver發布的行政區劃圖層
4.3測繪局提供的管線WMTS圖層+本地AGS發布的地形圖層
5.總結
通過此配置基本可以實現項目中遇到的絕大部分地圖需求。改進后,雖然不再需要對各種需求進行大規模的代碼編寫,但是針對不同的瓦片類型的URL獲取依然是要走分支的,這里可以通過策略模式來讓代碼更加規范。
斷了一個多月沒寫博,WebGIS的原理系列會繼續寫下去的,希望大家持續關注。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
很久以前就看到了Burp suite這個工具了,當時感覺好NB,但全英文的用起來很是蛋疼,網上也沒找到什么教程,就把這事給忘了。今天準備開始好好
學習這個滲透神器,也正好給大家分享下。(注:內容大部分是
百度的,我只是分享下自已的學習過程)
Burp Suite 是用于攻擊
web 應用程序的集成平臺。它包含了許多工具,并為這些工具設計了許多接口,以促進加快攻擊應用程序的過程。所有的工具都共享一個能處理并顯示HTTP 消息,持久性,認證,代理,日志,警報的一個強大的可擴展的框架。
Burp Suite 能高效率地與單個工具一起工作,例如: 一個中心站點地圖是用于匯總收集到的目標應用程序信息,并通過確定的范圍來指導單個程序工作。
在一個工具處理HTTP 請求和響應時,它可以選擇調用其他任意的Burp工具。例如:
代理記錄的請求可被Intruder 用來構造一個自定義的自動攻擊的準則,也可被Repeater 用來手動攻擊,也可被Scanner 用來分析漏洞,或者被Spider(網絡爬蟲)用來自動搜索內容。應用程序可以是“被動地”運行,而不是產生大量的自動請求。Burp Proxy 把所有通過的請求和響應解析為連接和形式,同時站點地圖也相應地更新。由于完全的控制了每一個請求,你就可以以一種非入侵的方式來探測敏感的應用程序。
當你瀏覽網頁(這取決于定義的目標范圍)時,通過自動掃描經過代理的請求就能發現安全漏洞。
IburpExtender 是用來擴展Burp Suite 和單個工具的功能。一個工具處理的數據結果,可以被其他工具隨意的使用,并產生相應的結果。
BurpSuite工具箱
Proxy——是一個攔截HTTP/S的代理服務器,作為一個在瀏覽器和目標應用程序之間的中間人,允許你攔截,查看,修改在兩個方向上的原始數據流。
Spider——是一個應用智能感應的網絡爬蟲,它能完整的枚舉應用程序的內容和功能。
Scanner[僅限專業版]——是一個高級的工具,執行后,它能自動地發現web 應用程序的安全漏洞。
Intruder——是一個定制的高度可配置的工具,對web應用程序進行自動化攻擊,如:枚舉標識符,收集有用的數據,以及使用fuzzing 技術探測常規漏洞。
Repeater——是一個靠手動操作來補發單獨的HTTP 請求,并分析應用程序響應的工具。
Sequencer——是一個用來分析那些不可預知的應用程序會話令牌和重要數據項的隨機性的工具。
Decoder——是一個進行手動執行或對應用程序數據者智能解碼編碼的工具。
Comparer——是一個實用的工具,通常是通過一些相關的請求和響應得到兩項數據的一個可視化的“差異”。
BurpSuite的使用
當Burp Suite 運行后,Burp Proxy 開起默認的8080 端口作為本地代理接口。通過置一個web 瀏覽器使用其代理服務器,所有的網站流量可以被攔截,查看和修改。默認情況下,對非媒體資源的請求將被攔截并顯示(可以通過Burp Proxy 選項里的options 選項修改默認值)。對所有通過Burp Proxy 網站流量使用預設的方案進行分析,然后納入到目標站點地圖中,來勾勒出一張包含訪問的應用程序的內容和功能的畫面。在Burp Suite 專業版中,默認情況下,Burp Scanner是被動地分析所有的請求來確定一系列的安全漏洞。
在你開始認真的工作之前,你最好為指定工作范圍。最簡單的方法就是瀏覽訪問目標應用程序,然后找到相關主機或目錄的站點地圖,并使用上下菜單添加URL 路徑范圍。通過配置的這個中心范圍,能以任意方式控制單個Burp 工具的運行。
當你瀏覽目標應用程序時,你可以手動編輯代理截獲的請求和響應,或者把攔截完全關閉。在攔截關閉后,每一個請求,響應和內容的歷史記錄仍能再站點地圖中積累下來。
和修改代理內截獲的消息一樣,你可以把這些消息發送到其他Burp 工具執行一些操作:
你可以把請求發送到Repeater,手動微調這些對應用程序的攻擊,并重新發送多次的單獨請求。
[專業版]你可以把請求發送到Scanner,執行主動或被動的漏洞掃描。
你可以把請求發送到Intruer,加載一個自定義的自動攻擊方案,進行確定一些常規漏洞。
如果你看到一個響應,包含不可預知內容的會話令牌或其他標識符,你可以把它發送到Sequencer 來
測試它的隨機性。
當請求或響應中包含不透明數據時,可以把它發送到Decoder 進行智能解碼和識別一些隱藏的信息。
[專業版]你可使用一些engagement 工具使你的工作更快更有效。
你在代理歷史記錄的項目,單個主機,站點地圖里目錄和文件,或者請求響應上顯示可以使用工具的任意地方上執行任意以上的操作。
可以通過一個中央日志記錄的功能,來記錄所單個工具或整個套件發出的請求和響應。
這些工具可以運行在一個單一的選項卡窗口或者一個被分離的單個窗口。所有的工具和套件的配置信息是可選為通過程序持久性的加載。在Burp Suite 專業版中,你可以保存整個組件工具的設置狀態,在下次加載過來恢復你的工具。
burpsuite專業版的個人感受
不知不覺使用burpsuite也有點年頭了。它在我日常進行安全評估,它已經變得日益重要。
現在已經變成我在日常滲透測試中不可缺少的工具之一。burpsuite官方現在已經更新到1.5,與之前的一點1.4相比。界面做了比較大的變化。而且還增加了自定義快捷鍵功能。burpsuite1.5缺點是對中文字符一如既往的亂碼。burpsuite入門的難點是:入門很難,參數復雜,但是一旦掌握它的使用方法,在日常工作中肯定會如虎添翼。
網上有破解版的下載,請自行百度。
一 快速入門(Burpsuite的安裝使用與改包上傳)
BlAck.Eagle
Burp suite 是一個
安全測試框架,它整合了很多的安全工具,對于滲透的朋友來說,是不可多得的一款囊中工具包,今天筆者帶領大家來解讀如何通過該工具迅速處理“截斷上傳”的漏洞。如果對該漏洞還不熟悉,就該讀下以前的黑客X檔案補習下了。
由于該工具是通過
Java寫的,所以需要安裝 JDK,關于 JDK 的安裝,筆者簡單介紹 下。基本是傻瓜化安裝,安裝完成后需要簡單配置下環境變量,右鍵 ”我的電腦”->”屬 性”->”高級”->”環境變量”,在系統變量中查找 Path,然后點擊編輯,把 JDK 的 bin 目 錄寫在 path 的最后即可。如圖 1
圖 1
通過 CMD 執行下 javac 看是否成功安裝。安裝配置成功則會顯示下圖信息。如圖 2
圖 2 這里的測試網址是筆者幫朋友測試某站點時獲取的,登陸后臺后可以發表新聞,但是只能上 傳圖片。然后發現新聞的圖片目錄在 upload/newsimg 下。如圖 3
圖 3
后臺有個” 網站資料設置”的功能,可以自行定義新聞圖片的路徑,但是當嘗試把新聞圖片
路徑改為”upload/newsimg.asp/”時,上傳圖片竟然上傳失敗了,所以這種方法失效。 如圖 4、
圖 4
接下來我們嘗試上傳截斷漏洞,可能大家經常用的是通過 WSockExpert 抓包,修改后通過 nc 來提交獲取 shell,但是大家會發現那樣有點繁瑣,我們看看今天的方法是多么高效: 首先允許 burpsuite.jar,然后點擊”proxy” 標簽,會發現 burp suite 默認的代理監聽端 口為 8080,如果怕跟自己電腦上的某個端口沖突的話,可以點擊”edit”進行編輯。筆者 使用默認端口。如圖 5
圖 5
然后打開本地瀏覽器的代理設置的地方,IE 一般為”工具”->”Internet 選項” –>”連 接”->”局域網設置”,其他瀏覽器大致相同。如圖 6
圖 6
設置代理之后,我們到后臺的某個上傳新聞圖片的地方上傳一個圖片木馬,我這里的圖片木 馬為asp 一句話。如圖 7
圖 7
點擊上傳之后,我們到 proxy 的”history”選項,會發現該網站的某個 POST 提交請求, 我們選中該鏈接后,右鍵選擇”send to repeater”,如圖 8
圖 8
我們會發現剛才提交的請求的數據包,那么我們看一下,有一項是上傳路徑的地方,我們以
前 進 行 截 斷 上 傳 的 時 候 經 常 修 改 這 個 地 方 , 這 次 也 不 例 外 , 我 們 把 上 傳 路 徑 改 為”upload/shell.asp ”后面有一個空格,如圖 9
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
1.測試對象
這次測了一些http接口和幾個網頁。
2.測試策略
2.1 基準
測試:單個調用各接口循環100次計算平均響應時間
2.2
性能測試:單個接口調用以50并發用戶數為單位,逐步加壓直到預估的實際負載300并發用戶,觀察測試指標變化
2.3
壓力測試:單個接口調用以50并發用戶數為單位,逐步加壓直到錯誤率過高或服務器資源使用率過高,觀察測試指標變化
2.4 負載測試:預估實際負載為300并發用戶數,在此基礎上持續測試5分鐘左右,觀察測試指標是否達標
2.5 穩定性測試:預估實際負載為300并發用戶數,在此基礎上持續測試60分鐘左右,觀察測試指標是否達標,重點觀察錯誤率
2.6 疲勞性測試:預估實際負載為300并發用戶數,在此基礎上持續測試240分鐘左右,觀察測試指標是否達標,重點觀察錯誤率
2.7 組合測試:對2.2-2.5的測試采用不同接口同時調用(即系統不同模塊同時測試)
2.8 其他:以不同ip地址加壓,測試服務器負載均衡效果。
以上,本次只做了2.2、2.3、2.4、2.8
3.測試指標
測響應時間、錯誤率;同時專人監控服務器硬件資源使用狀況、監控tomcat應用服務器等。
計算和監控吞吐量(測試工具自動計算測試執行過程中的吞吐量(每秒鐘處理請求數),同時服務器監控軟件業監控到了測試執行時服務器的吞吐量)
本次實際測試得到吞吐量距離預估有較大差距;錯誤率超出預期;且測試數據準備有一定問題。
4.測試工具
需設置語言為英文,默認中文翻譯不完整。
5.測試腳本編寫、調試
5.1 提前對接口、網頁進行錄制。每個待測接口、網頁需要加斷言。 斷言多采用JQuery斷言和Regular Expression斷言
5.2 重點在測試數據的準備。
5.3 采用了本地
web應用提供數據,jmeter獲取這些數據,再發送給服務器的方法(這次發現這個本地應用生成的數據在較高并發時有重復,導致了不必要的錯誤率)
5.4 測試結果監聽器: assertion results, summary report, aggregate report, result tree, result table
5.5 測試接口調用時,可用網頁、
數據庫等其他方法確認接口調用成功。觀察接口調用是否生效,是否和網頁同樣效果。
6.測試執行
6.1 一臺電腦加壓300-600并發用戶。如果需要更多則需要增加電腦。
6.2 以不同ip地址加壓,測試服務器負載均衡效果。
6.3 機房測試,排除internet網絡延遲問題
6.4 數據備份和還原,排除性能測試對數據的改變
6.5 生產環境測試(系統未上線),排除測試環境的影響
7.測試報告
7.1 截取了jmeter監聽器的結果,可以截取服務器監控的截圖
8.調優
本次測試結果不理想,服務器因硬件強大,幾乎無負載,但應用本身有
java出錯。并發現接口調用結果未正確影響網頁的bug。
后續需要等開發修復、優化之后再次測試
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一、什么是 ARC ?
所謂ARC就是Automatic Reference Counting , 即自動引用計數。ARC是自iOS5引入的。ARC機制的引入是為了簡化開發過程的內存管理的。相對于之前的MRC (Manual Reference Counting) , ARC機制顯得更加自動化。在使用ARC開發過程中,開發者只需考慮strong / weak 的使用,不再需要考慮對象何時要retain,release/autorealease。使用ARC一般不會降低程序的效率。
ARC一個很重要的原則是:只要某個對象被任一strong指針指向,那么它將不會被銷毀。如果對象沒有被任何strong指針指向,那么就將被銷毀。
ARC是基于引用計數的,當某個對象被一個strong指針指向時,它的計數+1。當沒有strong指針指向時,其計數為0,此時對象會被銷毀。只要一個對象有至少一個strong指針指向時,它就不會被銷毀。但ARC容易造成一個 Strong Reference Cycle 的問題,這樣即使AddBook 和 Entry 這兩個對象都不再使用了,但是由于ARC機制,這兩個對象都互相有strong指針指向,所以這兩個對象都不會被回收,從而造成內存無法被釋放。
針對上面的情況,有一種解決方法:在其中一個對象中引入weak,替換其strong
引入weak后,當entry使用完后,由于指向AddrBook沒有strong指針,所以AddrBook會首先被釋放,然后由于AddrBook被釋放,指向Entry的Strong指針也會銷毀,此時沒有指向Entry的strong指針,所以Entry也會被釋放。這樣就不會出現內存無法被釋放的情況。
這里就有一個問題了,什么時候應該用strong,什么時候應該用weak呢?看以下解析:
如圖所示,ViewController直接持有View,所以ViewController應該要有一個strong指向view。同理,view直接持有subviews,所以也應該要有strong指向subviews。由于viewcontroller要使用subviews對象,但又不想直接持有subviews,所以只好通過weak指向subviews。這樣的話,可以在viewcontroller中不改變view的持有關系,就可以使用subviews對象。從圖中可以得出一個通用的規律:對于有直接持有的關系,持有者要通過strong指向被持有者。對于有間接持有關系的,間接持有者需通過weak指向間接被持有者。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
Java遠程方法調用,即Java RMI(Java Remote Method Invocation)是Java編程語言里,一種用于實現遠程過程調用的應用程序編程接口。它使客戶機上運行的程序可以調用遠程服務器上的對象。遠程方法調用特性使Java編程人員能夠在網絡環境中分布操作。RMI全部的宗旨就是盡可能簡化遠程接口對象的使用。
一、創建RMI程序的4個步驟
1、定義一個遠程接口的接口,該接口中的每一個方法必須聲明它將產生一個RemoteException異常。
2、定義一個實現該接口的類。
3、創建一個服務,用于發布2中定義的類。
4、創建一個客戶程序進行RMI調用。
二、程序的詳細實現
1.首先我們先創建一個實體類,這個類需要實現Serializable接口,用于信息的傳輸。
1 import java.io.Serializable; 3 public class Student implements Serializable { 5 private String name; 7 private int age; 9 public String getName() { 11 return name; 13 } 15 public void setName(String name) { 17 this.name = name; 19 } 21 public int getAge() { 23 return age; 25 } 27 public void setAge(int age) { 29 this.age = age; 31 } 33 } |
2.定義一個接口,這個接口需要繼承Remote接口,這個接口中的方法必須聲明RemoteException異常。
1 import java.rmi.Remote;
3 import java.rmi.RemoteException;
5 import java.util.List;
6 public interface StudentService extends Remote {
12 List<Student> getList() throws RemoteException;
14 }
3.創建一個類,并實現步驟2中的接口,但還需要繼承UnicastRemoteObject類和顯示寫出無參的構造函數。
1 import java.rmi.RemoteException; 3 import java.rmi.server.UnicastRemoteObject; 5 import java.util.ArrayList; 7 import java.util.List; 11 public class StudentServiceImpl extends UnicastRemoteObject implements 13 StudentService { 15 public StudentServiceImpl() throws RemoteException { 17 } 21 public List<Student> getList() throws RemoteException { 23 List<Student> list=new ArrayList<Student>(); 25 Student s1=new Student(); 27 s1.setName("張三"); 29 s1.setAge(15); 31 Student s2=new Student(); 33 s2.setName("李四"); 35 s2.setAge(20); 37 list.add(s1); 39 list.add(s2); 41 return list; 43 } 45 } |
4.創建服務并啟動服務
1 import java.rmi.Naming; 2 import java.rmi.registry.LocateRegistry; 4 public class SetService { 6 public static void main(String[] args) { 8 try { 10 StudentService studentService=new StudentServiceImpl(); 12 LocateRegistry.createRegistry(5008);//定義端口號 14 Naming.rebind("rmi://127.0.0.1:5008/StudentService", studentService); 16 System.out.println("服務已啟動"); 18 } catch (Exception e) { 20 e.printStackTrace(); 22 } 24 } 26 } |
5. 創建一個客戶程序進行RMI調用。
1 import java.rmi.Naming; 3 import java.util.List; 5 public class GetService { 9 public static void main(String[] args) { 11 try { 13 StudentService studentService=(StudentService) Naming.lookup("rmi://127.0.0.1:5008/StudentService"); 15 List<Student> list = studentService.getList(); 17 for (Student s : list) { 19 System.out.println("姓名:"+s.getName()+",年齡:"+s.getAge()); 21 } 23 } catch (Exception e) { 25 e.printStackTrace(); 27 } 29 } 33 } |
6.控制臺顯示結果
=============控制臺============
姓名:張三,年齡:15
姓名:李四,年齡:20
===============================
在Spring中配置Rmi服務
將Rmi和Spring結合起來用的話,比上面實現Rmi服務要方便的多。
1.首先我們定義接口,此時定義的接口不需要繼承其他接口,只是一個普通的接口
1 package service;
3 import java.util.List;
5 public interface StudentService {
7 List<Student> getList();
9 }
2.定義一個類,實現這個接口,這個類也只需實現步驟一定義的接口,不需要額外的操作
1 package service; 4 import java.util.ArrayList; 6 import java.util.List; 9 public class StudentServiceImpl implements StudentService { 11 public List<Student> getList() { 13 List<Student> list=new ArrayList<Student>(); 15 Student s1=new Student(); 17 s1.setName("張三"); 19 s1.setAge(15); 21 Student s2=new Student(); 23 s2.setName("李四"); 25 s2.setAge(20); 27 list.add(s1); 29 list.add(s2); 31 return list; 33 } 35 } |
3.接一下來在applicationContext.xml配置需要的信息
a.首先定義服務bean
<bean id="studentService" class="service.StudentServiceImpl"></bean>
b.定義導出服務
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"
p:service-ref="studentService"
p:serviceInterface="service.StudentService"
p:serviceName="StudentService"
p:registryPort="5008"
/>
也可以增加p:registryHost屬性設置主機
c.在客戶端的applicationContext.xml中定義得到服務的bean(這里的例子是把導出服務bean和客戶端的bean放在一個applicationContext.xml中的)
<bean id="getStudentService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="rmi://127.0.0.1:5008/StudentService"
p:serviceInterface="service.StudentService"
/>
d.配置的東西就這么多,是不是比上面的現實要方便的多呀!現在我們來測試一下
1 package service; 2 import java.util.List; 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 public class Test { 6 public static void main(String[] args) { 7 ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); 8 StudentService studentService=(StudentService) ctx.getBean("getStudentService"); 9 List<Student> list = studentService.getList(); 10 for (Student s : list) { 11 System.out.println("姓名:"+s.getName()+",年齡:"+s.getAge()); 12 } 13 } 14 } |
=============控制臺============
姓名:張三,年齡:15
姓名:李四,年齡:20
=============================
上面的mian方法運行可能會報錯,應該是spring的jar少了,自己注意添加。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
關于<<提高代碼質量系列>>
這是我新開的一個系列,旨在記錄我對整個編碼規范,代碼風格,語法習慣,架構設計的一些思考,感悟和總結.
前言
不知道大家會不會覺得我的標題很噱頭,不是一般應該提倡寫注釋的么?首先我得解釋下,我這句話有兩個意思!
1,絕非提倡不寫注釋,而是不要寫不必要的注釋.
2,命名規范的作用大于注釋
好吧,這么一說,其實還是有點噱頭的感覺的,因為我這篇文其實重心更放在強調命名規范和設計規范上面,良好的規范,讓你的代碼有自釋性,省去了注釋的步驟.
還要強調下的是:這個觀點絕非我自己主觀臆斷,憑空瞎想出來的. 而是實實在在由項目開發里面總結出來的.
為什么我有這個想法呢?請繼續看我的蛋疼經歷.
正文
先上段奇葩代碼
/// <summary> /// 根據產品ID獲取產品列表 /// </summary> /// <param name="columnID">關鍵字</param> public DataTable GetColumnInfoByColumnID(int columnID) { return DALColumn.GetColumnInfoByColumnID(columnID); } /// <summary> /// 根據產品名稱獲取產品列表 /// </summary> /// <param name="columnID">關鍵字</param> public DataTable GetColumnInfoByColumnID(string columnName) { return DALColumn.GetColumnInfoByColumnID(columnName); } |
這是我現在維護的一個老項目了,經手的人比較多,代碼寫的比較垃圾,我們先不吐槽這種純粹是脫褲子放屁的所謂三層和明明返回的是dataTable又扯什么info,就說這兩個函數,tm功能應該是不一樣的吧,為啥名字一模一樣?這是在鬧哪樣?
其實,造成這種情況的原因,我們都知道,就是某類程序員的ctrl c+v大法,這兩個函數底層邏輯比較相似,懶得重構,直接copy了改改多快!copy就copy吧,好歹名字改一下啊!也許這位前輩會說,我不是寫了注釋了嗎,看到注釋,不就知道這個函數是干嘛的了?但問題是,其他調用的人,首先看到的,肯定是函數名啊,GetColumnInfoByColumnID,多直觀 通過id來查找唄.雖然這里有兩個比較違和的地方,一是參數默認名字是columnName,二是參數類型不是int而是string,但反正這項目的代碼不規范,如果調用的人也夠粗心,那么,一個隱晦的bug,就這么產生了.
如果改下名字,叫 GetTableByColumnName,這種錯誤發生的概率無疑會減少很多了,
Ps:其實現在ide功能這么強大,只要你確定沒有反射調用這個函數的地方,完全可以使用全局重命名的方法,一步到位.
看到這里讀者朋友們可能會說,這是命名不規范嘛,和寫不寫注釋有什么關系呢?
我們可以假想一種這樣的情況,同樣是拷貝代碼,拷貝者改了函數名,這個名字語義清晰,表意清楚, 但他卻忘記改注釋了,結果函數是新的函數簽名,函數的注釋卻是其他一段莫名其妙的注釋.
或者再假想一種情況.
原來的一個函數,名字和注釋是對應的,隨便舉個例子,叫GetTypeByColumnId吧,注釋為"通過產品id取得產品類型"
一切ok,是吧! 但是現在邏輯變了,比如說Type和產品無關了,需要通過生產批次來確定,于是一個代碼維護者,將函數重寫了一下功能ok,滿足新需求!!同時他比上個人好一點,他記得改函數名,然后他改為了GetTypeBySerialId,這名字也很ok,一切也都看起來很好.但偏偏他漏掉了注釋,但代碼仍然運行的很ok,畢竟注釋可不受.net的元數據的支持,ide也不可能知道你這里犯了這么一個錯誤是吧!
然后,接下來的場景大家很容易就可以聯想到,
如果是細心的調用者:
尼瑪!函數注釋讓我傳"產品id",但函數名和參數默認名字又是"SerialId"(流水號),這尼瑪鬧哪樣?
如果是粗心的調用者呢?兩種情況唄:
1.看了函數名字和默認參數名字 ,沒注意到注釋,ok,算這個粗心的小伙伴幸運,
2.看了注釋,然后這小伙伴不認識SerialId這個單詞的意思. 然后,一個悲傷的故事就這么發生了!!
其實我以上舉例的一些函數,都是功能比較具體,業務比較單純,也容易用幾個單詞描述.對于這些函數,我個人的意見是,完全不需要去寫注釋,
有以下幾個原因:
1.浪費精力去寫,
2.調用的人需要把一段話讀兩遍(函數名和注釋)
3.寫了還需要人去維護,(改了代碼,得同步去改注釋)
4.如果強類型的參數傳遞不匹配,ide或者resharper插件會馬上指出你的錯誤,但如果注釋和代碼不匹配,則除了通過人力CodeReview,沒有其他任何辦法去找出這種錯誤.
其中尤其是4,完全就是埋在項目中的地雷,除非你踩到了,不然很難排查.
當然,如果是反之,邏輯復雜,甚至有調用的前置約束,那肯定該寫注釋還是得寫了.
同時,這里還提一個觀點,注釋比代碼更有價值,因為代碼畢竟大部分還是在講怎么做(how do),而注釋是講做什么(do what)?抽象程度更高,對于比較復雜的代碼,如果有注釋,調用者一般都會優先去閱讀注釋,而不是去閱讀代碼,所以:輕易不寫注釋,但如果你寫了,請一定要對你的注釋負責,它比代碼更需要你的細心呵護!!
好吧,我會說這篇文最大的作用,其實是可以讓我吐槽發泄一下么?
我感覺自己現在就是在這種地雷坑里,天天過著提醒吊膽的日子.
以上例子皆非我刻意編造,來源于工作中的真實經歷,只是稍作修飾,隱去了和業務有關的信息.
根據回復的補充:
相信很多博友有這樣的經歷,這個函數好復雜呀!我必須寫注釋啊,不然一段時間之后,我自己都看不明白了,
有些時候是確實很復雜,但也有時候是設計的問題,這時候,寫注釋其實是一種逃避了.逃避你需要繼續深入思考這個函數的業務和功能的設計是否合理.
其實我這標題還有第三個意思:
在盡可能少寫注釋的前提下,如果一個函數的注釋仍然超過了2處,那么我認為這個函數的設計是有問題的.
這個函數是否太大,是否是反模式里面提到的萬應靈或屠龍術?
所以有時候說的 "因為業務復雜,很難用幾個單詞去描述,所以很難命名",就是如此.
你的業務抽象粒度是否太大?導致一個業務模塊承擔了過多的業務.
你是否把幾個流程放在了一個節點上?導致引入了額外的邏輯判斷甚至是多余的邏輯分支.
如果是這樣,你想用幾個單詞來描述這么復雜的邏輯,當然是不可能了.
這點我會在后續的重構系列里面談談我自己的理解.
補充下自己的理解.
我始終認為好的設計,大部分情況下,函數名就足以解釋它的功能,如果你遇到了兩三個單詞不能解釋函數功能的情況----說明你該分解函數了!
比如一個大函數,OutPutMetaData,輸入是源數據路徑,使用的模板 返回解析之后的元數據
流程大概是 采集數據->分析數據->匹配模板->生成MetaData
代碼是大約1-2k行,如果寫在一個函數里面,當然也似可以的,但你想用注釋解釋清楚,必須在每個流程的關鍵節點寫注釋,遇到數據有前后關聯關系的,還得思維反復跳來跳去.
但如果寫成下面這種模式的代碼,基本就像是閱讀英文說明(注釋),一樣閱讀代碼了
public MetaData OutPutMetaData(string sourcePath, MetaTemplate template) { var metaDataFactory = new MetaDataFactory(); if (!metaDataFactory.CheckInput(sourcePath)) { throw new ErrorSourceException(); } if (!metaDataFactory.TransformSourceData(template)) { throw new ErrorFormatException(); } return metaDataFactory.CreateMetaData(); } |
這樣寫雖然多了很多的類和方法,還要額外定義一些中間數據的實體類型和自定義異常,但是經過合理的封裝和命名之后,整個結構非常清晰,定位錯誤和修改流程也方便.
代碼閱讀速度基本和描述語句(注釋)的閱讀速度相當, 這就是代碼即注釋.
最后總結:
其實我認為最關鍵是要形成自己的編碼規范,這個"規范"不僅僅指的是狹義的命名規則和代碼格式,縮進,文件組織結構等.更關鍵的是,要形成一套有邏輯性,能自洽,有良性導向的一套思維模式,并時刻堅持遵守它,思考它,改進它.
這套思維模式你可以自由的去擴展,只要不偏離它的中心思想.比如我給自己擴展的一些要求:
1.函數一律使用動賓結構,如InitFactory,而不用FactoryInit,其實這兩者沒什么優劣,僅僅只是讓自己習慣,以后思考和閱讀自己代碼的時候,能更快的帶入過去的自己的思維,更快的理解自己的代碼,同時找api也能節約一點時間.
2.html標簽樣式id小寫開頭,class大寫開頭,同理,其實也沒啥原因,就是個習慣.
3.描述一個事物的時候要區分是what it is(名詞,形容詞)還是what it can do(動詞,動名詞,動賓短語)比如doClose, 是某個事物的動作,closing,和 closed則代表它的狀態
再就是結構設計上的一些感覺了,這個比較抽象,不好用文字很準確的描述,大致意思就是我會從一些緯度對功能進行切分,access business viewModel show interaction 等,不一定都能分的非常清楚,也不強求一個區分度很高的邊際,但至少要有個模糊的定位.
如果能長期堅持下來,以后閱讀自己的代碼是非常容易的,即使是沒有(少量)注釋.
Ps:錘煉自己的這套思維模式的方法也很簡單,也就三點
多看優秀代碼,自己動手多寫,多思考總結.
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一、Webbench簡單介紹
在一個網站上線前, 通常我們應該做一些相關的
壓力測試, 以便了解當前
Web服務器在高并發高負載情況下的響應狀況和速度,方便對Web服務器進行優化和重構。目前有很多免費的web壓力測試工具可以幫助我們完成測試, 例如: 十個免費的Web壓力測試工具http://coolshell.cn/articles/2589.html,但在真實項目中使用Apache ab和Webbench來完成壓力測試。Apache的優點:Apache的ab使用非常簡單, 而且只要是安裝了Apache了,就會自帶其ab工具,缺點:就是不能模擬高并發狀態下的測試, 好像最多可以模擬100-200次/秒的并發. 如果需要模擬更高負載的壓力測試, 就需要使用Webbench。
Webbench是有名的網站
壓力測試工具,它是由 Lionbridge公司(http://www.lionbridge.com)開發。Webbech能測試處在相同硬件上,不同服務的性能以及不同硬件上同一個服務的運行狀況。webBech的標準測試可以向我們展示服務器的兩項 內容:每秒鐘相應請求數和每秒鐘傳輸數據量。webbench不但能具有便準靜態頁面的測試能力,還能對動態頁面(ASP,PHP,JAVA,CGI)進行測試的能力。還有就是他支持對含有SSL的安全網站例如電子商務網站進行靜態或動態的
性能測試,webbench最多可以模擬3萬個并發連接去測試網站的負載能力。缺點測試的結果太簡單了。
二、安裝Webbench
注意點:為了測試準確,請將 webbench 安裝在別的linux服務器上,(因為webbench 做壓力測試時,自身也會消耗CPU和內存資源, 否則很可能把自己服務器搞掛掉)
目前Webbench最新的版本為webbench-1.5.tar.gz下載地址 http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz
1.先安裝依賴包:yum install ctags
2.安裝Webbench:
tar zxvfwebbench-1.5.tar.gz
cd webbench-1.5
make &&make install
如果出現以下報錯信息:
ctags *.c /bin/sh: ctags: command not found make: [tags] Error 127 (ignored) install -s webbench /usr/local/bin install -m 644 webbench.1 /usr/local/man/man1 install: cannot create regular file `/usr/local/man/man1': No such file ordirectory make: *** [install] Error 1 |
解決方法:
mkdir -p /usr/local/man
chmod 644 /usr/local/man
再次執行make && make install
看到如下界面,說明安裝成功
make: Nothing to be done for `all'. install -s webbench /usr/local/bin install -m 644 webbench.1/usr/local/man/man1 install -d /usr/local/share/doc/webbench install -m 644 debian/copyright/usr/local/share/doc/webbench install -m 644 debian/changelog/usr/local/share/doc/webbench |
三、使用
[root@centos ~]# webbench -c 400 -t 20 http://10.43.2.192/ Webbench - Simple Web Benchmark 1.5 Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software. Benchmarking: GET http://10.43.2.192/ 400 clients, running 20 sec. Speed=392676 pages/min, 1603427 bytes/sec. Requests: 130892 susceed, 0 failed. |
參數說明:-c表示并發數,-t表示時間(秒)
每秒鐘傳輸數據量:1603427 bytes/sec每秒鐘相應請求數:392676/60= 6544 pages/sec
這里有一個特別要注意的點:10.43.2.192/后面的“/”一定不要忘記
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters