这篇博客谈一下在实际项目中咱们如何执行重构。react
首先咱们明确一下重构的目标是什么?重构是为了让项目中的代码易懂,易维护。我以为有一些像家居中的收纳。假设你有一个抽屉,如今你只有同样东西。那么须要去整理收纳吗?其实意义不大,由于任何人只要打开抽屉,就能知道里面装了什么。可是随着业务需求的增加,抽屉里的东西愈来愈多,往里面放东西的人也愈来愈多。终于过了一个临界点,任何一我的要往抽屉里找东西都愈来愈难。swift
因此咱们须要保持秩序。这是收纳,也是重构。app
class CustomMapOverlayView: UIView {
var polygonEditPointViews = [PolygonAnnotationView]()
var polygonLayer = CAShapeLayer()
var distanceMarkerLayer = CAShapeLayer()
private func drawPolygon(currentIndex: Int = 0, showDistanceMarker: Bool = true) {
guard polygonControlPointViews.count >= 3 else { return }
polygonEditPointViews.forEach { $0.removeFromSuperview() }
polygonEditPointViews.removeAll()
var distanceMarkerLayers = [CAShapeLayer]()
let polygonPath = UIBezierPath()
for (index, polygonControlPointView) in polygonControlPointViews.enumerated() {
if index == 0 {
polygonPath.move(to: polygonControlPointView.center)
}
let nextIndex = (index + 1) % polygonControlPointViews.count
let nextControlPoint = polygonControlPointViews[nextIndex].center
polygonPath.addLine(to: nextControlPoint)
let editPoint = GeometryHelper.getMiddlePoint(point1: polygonControlPointView.center, point2: polygonControlPointViews[nextIndex].center)
addPolygonPointView(center: editPoint, type: .add)
if showDistanceMarker {
let nextCoordinate = currentPolygonVertexes[nextIndex].coordinate
let markerDistance = GeometryHelper.getDistance(from: currentPolygonVertexes[index].coordinate, to: nextCoordinate).rounded()
let markerLayer = drawDistanceMarkerLayer(centerPoint: editPoint, text: String(format: "%.0f", markerDistance) + "m")
distanceMarkerLayers.append(markerLayer)
}
}
polygonPath.close()
polygonLayer.removeFromSuperlayer()
polygonLayer.path = polygonPath.cgPath
polygonLayer.lineWidth = 1
// 判断多边形是否合法,不合法则将线段以红色显示
polygonLayer.strokeColor = isPolygonValid(index: currentIndex) ? polygonColor.cgColor : UIColor.red.cgColor
polygonLayer.fillColor = polygonColor.cgColor
polygonLayer.zPosition -= 1
layer.addSublayer(polygonLayer)
// 添加距离标记
distanceMarkerLayer.sublayers?.removeAll()
distanceMarkerLayer.removeFromSuperlayer()
distanceMarkerLayers.forEach {
distanceMarkerLayer.addSublayer($0)
}
distanceMarkerLayer.zPosition -= 1
layer.addSublayer(distanceMarkerLayer)
}
private func drawDistanceMarkerLayer(centerPoint: CGPoint, text: String) -> CAShapeLayer {
let textSize = getTextSize(text: text)
let react = CGRect(x: centerPoint.x - 8, y: centerPoint.y - 8, width: textSize.width + 24, height: 16)
let roundRectPath = UIBezierPath(roundedRect: react, cornerRadius: 8)
let markerLayer = CAShapeLayer()
markerLayer.path = roundRectPath.cgPath
markerLayer.fillColor = UIColor.white.cgColor
let textLayer = drawTextLayer(frame: CGRect(x: react.origin.x + 18, y: react.origin.y + (8 - textSize.height/2), width: textSize.width, height: textSize.height), text: text, foregroundColor: MeshColor.grey2, backgroundColor: UIColor.clear)
markerLayer.addSublayer(textLayer)
return markerLayer
}
}
复制代码
上面这段代码很是明显的 bad smell 就是太长,大概有四十行。一般状况下一个方法长度超过 20 行意味着作了太多事。固然也有一些状况方法长一点是能够接受的。假设咱们有一个抽屉,抽屉装的都是同同样东西,虽然把抽屉装满了,可是对于这个抽屉里装了什么仍是一目了然。若是方法长,可是方法里只是单一的作相似的、很容易理解的事也能够接受。post
上面代码第二个问题是代码中的抽象层次不一致。我举个例子,假设公司的 CEO 作了一个决策,他打算通知全部高管,而后高管再逐级同步给部门。可是 CEO 在通知完高管后,询问高管,这个决策你要通知的人有谁。高管说要通知 A、B、C。因而 CEO 在高管会上把 A、B、C 叫来告诉了他们这个决策。代码的抽象层级也是相似,原本在处理顶层的逻辑,接着代码直接去处理了下一层的细节。这样不一样层级的代码在一个方法里会加大理解的难度。 如今咱们开始一步步重构这段代码。测试
若是你们看了前面几篇地图的控件设计实现的文章,会发现这个方法还有一个结构上的问题。多边形的顶点位置是从 polygonEditPointViews 上取的。可是若是仔细思考一下,其实这个方法依赖的是顶点的位置,如今经过依赖 polygonEditPointViews 间接获得,这样多了没必要要的依赖。多了这层没必要要的依赖会增长代码的不稳定性,另外若是要隔离测试这个方法,隔离的代价也会更高。ui
那么咱们首先作一个小改动,移除对 polygonEditPointViews 的依赖。能够修改方法的参数,把顶点坐标当作参数传进来。若是类的规模小,直接封装一个属性提供顶点坐标也能够。这里我选择比较直观的封装属性方式隔离。spa
class CustomMapOverlayView: UIView {
var polygonEditPointViews = [PolygonAnnotationView]()
private var areaVertexs: [CGPoint] {
return polygonControlPointViews.map { $0.center }
}
private func drawPolygon(currentIndex: Int = 0, showDistanceMarker: Bool = true) {
guard areaVertexs.count >= 3 else { return }
polygonEditPointViews.forEach { $0.removeFromSuperview() }
polygonEditPointViews.removeAll()
var distanceMarkerLayers = [CAShapeLayer]()
let polygonPath = UIBezierPath()
for (index, vertex) in areaVertexs.enumerated() {
if index == 0 {
polygonPath.move(to: vertex)
}
let nextIndex = (index + 1) % areaVertexs.count
let nextControlPoint = areaVertexs[nextIndex]
polygonPath.addLine(to: nextControlPoint)
let editPoint = GeometryHelper.getMiddlePoint(point1: vertex, point2: areaVertexs[nextIndex])
addPolygonPointView(center: editPoint, type: .add)
// ...
}
}
// ...
}
}
复制代码
这样代码的可读性也好了一点,读的时候不要去关心 polygonEditPointViews。设计
这段代码主要作了三件事:绘制多边形,在多边形边的中点显示边距,在边上添加增长点的按钮。实现的时候三件事的实现细节又写在了一块儿。所以读起来感受代码有多有乱。code
咱们首先隔离绘制多边形的代码。orm
var polygonLayer = CAShapeLayer()
private func drawPolygon(currentIndex: Int = 0, showDistanceMarker: Bool = true) {
guard areaVertexs.count >= 3 else { return }
renderPolygonLayer(changedPointIndex: currentIndex)
polygonEditPointViews.forEach { $0.removeFromSuperview() }
polygonEditPointViews.removeAll()
var distanceMarkerLayers = [CAShapeLayer]()
for (index, vertex) in areaVertexs.enumerated() {
let nextIndex = (index + 1) % areaVertexs.count
let editPoint = GeometryHelper.getMiddlePoint(point1: vertex, point2: areaVertexs[nextIndex])
addPolygonPointView(center: editPoint, type: .add)
if showDistanceMarker {
let nextCoordinate = currentPolygonVertexes[nextIndex].coordinate
let markerDistance = GeometryHelper.getDistance(from: currentPolygonVertexes[index].coordinate, to: nextCoordinate).rounded()
let markerLayer = drawDistanceMarkerLayer(centerPoint: editPoint, text: String(format: "%.0f", markerDistance) + "m")
distanceMarkerLayers.append(markerLayer)
}
}
// 添加距离标记
distanceMarkerLayer.sublayers?.removeAll()
distanceMarkerLayer.removeFromSuperlayer()
distanceMarkerLayers.forEach {
distanceMarkerLayer.addSublayer($0)
}
distanceMarkerLayer.zPosition -= 1
layer.addSublayer(distanceMarkerLayer)
}
private func renderPolygonLayer(changedPointIndex: Int = 0) {
let polygonPath = UIBezierPath()
polygonPath.move(to: areaVertexs[0])
for index in 1 ..< areaVertexs.count {
let nextIndex = (index + 1) % areaVertexs.count
let nextControlPoint = areaVertexs[nextIndex]
polygonPath.addLine(to: nextControlPoint)
}
polygonPath.close()
polygonLayer.removeFromSuperlayer()
polygonLayer.path = polygonPath.cgPath
polygonLayer.lineWidth = 1
// 判断多边形是否合法,不合法则将线段以红色显示
polygonLayer.strokeColor = isPolygonValid(index: changedPointIndex) ? polygonColor.cgColor : UIColor.red.cgColor
polygonLayer.fillColor = polygonColor.cgColor
polygonLayer.zPosition -= 1
layer.addSublayer(polygonLayer)
}
复制代码
把绘制多边形的代码抽离出来后逻辑已经清晰不少了。
接着咱们先重构一下 drawDistanceMarkerLayer
方法。这个方法有两个问题:
重构完成后调用的地方是这样的:
let markerLayer = createDistanceMarkerLayer(centerPoint: editPoint, markerDistance: markerDistance)
//原来的调用
let markerLayer = drawDistanceMarkerLayer(centerPoint: editPoint, text: String(format: "%.0f", markerDistance) + "m")
复制代码
接着咱们把距离标记再抽出来。
private func drawPolygon(currentIndex: Int = 0, showDistanceMarker: Bool = true) {
guard areaVertexs.count >= 3 else { return }
renderPolygonLayer(changedPointIndex: currentIndex)
polygonEditPointViews.forEach { $0.removeFromSuperview() }
polygonEditPointViews.removeAll()
for (index, vertex) in areaVertexs.enumerated() {
let nextIndex = (index + 1) % areaVertexs.count
let editPoint = GeometryHelper.getMiddlePoint(point1: vertex, point2: areaVertexs[nextIndex])
addPolygonPointView(center: editPoint, type: .add)
}
if showDistanceMarker {
renderDistanceMarkerLayer()
}
}
private func renderDistanceMarkerLayer() {
var distanceMarkerLayers = [CAShapeLayer]()
for index in 0 ..< areaVertexs.count {
let nextIndex = (index + 1) % areaVertexs.count
let middlePoint = GeometryHelper.getMiddlePoint(point1: areaVertexs[index], point2: areaVertexs[nextIndex])
let nextCoordinate = currentPolygonVertexes[nextIndex].coordinate
let markerDistance = GeometryHelper.getDistance(from: currentPolygonVertexes[index].coordinate, to: nextCoordinate).rounded()
let markerLayer = createDistanceMarkerLayer(centerPoint: middlePoint, markerDistance: markerDistance)
distanceMarkerLayers.append(markerLayer)
}
// 添加距离标记
distanceMarkerLayer.sublayers?.removeAll()
distanceMarkerLayer.removeFromSuperlayer()
distanceMarkerLayers.forEach {
distanceMarkerLayer.addSublayer($0)
}
distanceMarkerLayer.zPosition -= 1
layer.addSublayer(distanceMarkerLayer)
}
复制代码
作完这一步 drawPolygon
里的代码行数已经不多了,只有不到 10 行。在这个体量下前面说到旧代码问题的第二点就比较明显了:中间的绘制增长点的按钮和其余的层次不一样,绘制增长点直接把实现写在这里了,抽象层次直接下降了。一个顶层方法应该负责调度,细节的实现不该该在里面。
最后咱们把绘制增长点的按钮抽离出来。
private func drawPolygon(currentIndex: Int = 0, showDistanceMarker: Bool = true) {
guard areaVertexs.count >= 3 else { return }
renderPolygonLayer(changedPointIndex: currentIndex)
renderEditPoints()
if showDistanceMarker {
renderDistanceMarkerLayer()
}
}
private func renderEditPoints() {
polygonEditPointViews.forEach { $0.removeFromSuperview() }
polygonEditPointViews.removeAll()
for (index, vertex) in areaVertexs.enumerated() {
let nextIndex = (index + 1) % areaVertexs.count
let editPoint = GeometryHelper.getMiddlePoint(point1: vertex, point2: areaVertexs[nextIndex])
let polygonPoint = createPolygonPoint(center: editPoint, type: .add)
addSubview(polygonPoint)
polygonEditPointViews.append(polygonPoint)
}
}
复制代码
完成后核心方法 drawPolygon
只有 5 行代码,这个方法作了什么应该很是清晰易理解了。子方法中负责各自绘制的部分。若是后期要绘制其余元素,在 drawPolygon
中增长。若是元素的 UI 有变化,到各个负责具体绘制的方法中修改也不会影响到其余模块。
重构的指导思想是什么?按照一种逻辑整理划分代码,把每块代码的体量控制在一个容易理解的范围里。