Upload
blaise-hawkins
View
220
Download
0
Embed Size (px)
Citation preview
How to make the most of Code Analysis
Patrick Smacchia
NDepend Creator and Lead Developer
Build Stuff Lithuania - 11th Dec 2013
Introduction to NDependContract, Unit Test and Code CoverageDesigning to make the UI testableDetect regressions early with Code RuleOn Clean Code Structure
Introduction to NDepend
Tool for .NET Developers I’ve created in April 2004Became commercial in February 2007Tool specialized in static analysis of .NET code
Integrate in Visual Studio (2013, 2012, 2010, 2008) Integrate in Build Process to produce reports
Soon 4.000 companies client worldwide JArchitect (for Java) CppDepend (for C++)
NDepend Facts
Code Rules written with C# LINQ syntaxDependency Graph and MatrixCode DiffCode Metrics, Treemap Trending Code Coverage…
NDepend Features
How do we use NDepend to build NDepend?
Contract, Unit Test, Code Coverage
Microsoft Code Contract librarySuitable for the public surface of your product APIStandardized (documentation, compiler check…)Not adapted for an intensive usage (slow compilation)
System.Diagnostics.Debug.Assert()Adapted for an intensive usage, everywhere in your codeOnly work in DEBUG modeAt least it doesn’t slow down production execution
Code Contracts in .NET
Pretty much the same thing. Really!!
In both cases we want to check for assertions. In both cases we want a failure if a condition is not
fulfilled, because it means correctness violationCode Contracts MUST fail if not fulfilled at unit test
running timeAdvantage: You can run an automatic test with few
assertions, but still get the contracts assertions verified.
Code Contracts vs. Unit Tests
100% ! … less is not enough!Often we hear: the last 10% are too costly to be coveredWhy is it too costly?
Because this last 10% code is not well testableHence it is not well designedHence it is error-prone
Nobody wants to let the most error-prone code uncovered by test, do you?
How much Code Coverage is needed?
100% … at least needed for core business logic classes that contain the application complex logic
NDepend entire code base: 79% covered100% coverage also means all contracts get checked Isolate in some special classes uncoverable code
Blocking methods: MessageBox.Show() OpenFileDialog()
Hard to repro error cases: IO.UnauthorizedException catch
…
How much Code Coverage is needed?
3 major .NET tools to obtain Code Coverage from automatic tests run: VS Code CoverageJetBrains dotCoverNCover
Ndepend can import Code Coverage from any of these tools
Code Coverage
NDepend rules and Code Coverage
// <Name>Types tagged with FullCoveredAttribute should be 100% covered</Name>warnif count > 0 from t in Application.Types where t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) && t.PercentageCoverage < 100 let notFullCoveredMethods = t.Methods.Where( m => m.NbLinesOfCode> 0 && m.PercentageCoverage < 100 && !m.HasAttribute("NDepend.Attributes.UncoverableByTestAttribute".AllowNoMatch())) select new { t, t.PercentageCoverage, t.NbLinesOfCodeNotCovered, notFullCoveredMethods, t.NbLinesOfCode, t.NbLinesOfCodeCovered }
NDepend rules and Code Coverage
// <Name>Types 100% covered should be tagged with FullCoveredAttribute</Name>warnif count > 0 from t in JustMyCode.Types where !t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) && t.PercentageCoverage == 100 && !t.IsGeneratedByCompilerselect new { t, t.NbLinesOfCode }
NDepend rules and Code Coverage
// <Name>From now, all types added or refactored should be 100% covered by tests</Name>warnif count > 0 from t in JustMyCode.Types where // Match methods new or modified since Baseline for Comparison... (t.WasAdded() || t.CodeWasChanged()) && // ...that are not 100% covered by tests t.PercentageCoverage < 100 let methodsCulprit = t.Methods.Where(m => m.PercentageCoverage < 100) select new { t, t.PercentageCoverage, methodsCulprit }
NDepend rules and Code Coverage
// <Name>Types that used to be 100% covered but not anymore</Name>warnif count > 0from t in JustMyCode.Types where t.IsPresentInBothBuilds() && t.OlderVersion().PercentageCoverage == 100 && t.PercentageCoverage < 100let culpritMethods = t.Methods.Where(m => m.PercentageCoverage < 100)select new {t, t.PercentageCoverage, culpritMethods }
Designing to make the UI testable
A kernel object, referencing a grape of objects, that is shared amongst all UI panels
The kernel hold access toApplication states (currently running an analysis?, session opened? …)
Application context (user preferences, licensing options, theme…)
Application actions (open/close session, show/hide panel…)
Session states (analysis result loaded, diffed?…)
Session actions (build a graph, edit a code query…)
…
NDepend UI Design
NDepend UI Design
Suitable to write UI integration tests, that pilot the UI.Not much assertions are done in unit-tests bodies ……but thousands of code contracts nested in UI code are
covered by test run. Hence they are checked!
NDepend UI Design and Tests
foreach (var panelKind in new[] { PanelKind.StartPage, PanelKind.ProjectProperties, PanelKind.Matrix, PanelKind.Graph…}) { InvokeOnUIThread( () => m_Kernel.App.Actions.SetActivePanel(EventSender.Main, panelKind)); InvokeOnUIThread( () => m_Kernel.App.Actions.ShowPanel(EventSender.Main, panelKind));}
NDepend UI Design RuledGeneric rules can be written to enforce design decisions
like: Panels shouldn’t use each other
Demo
//<Name>Panels shouldn't use each others</Name>warnif count > 0let panelsNamespaces = Application.Namespaces .WithNameWildcardMatch("NDepend.UI.Panels.*") from nUser in panelsNamespaces.UsingAny(panelsNamespaces)from nUsed in panelsNamespaces.UsedByAny(panelsNamespaces)where nUser.IsUsing(nUsed) select new { nUser, nUsed }
Detect Regressions early with Code Rules
Code Rules like Types that used to be 100% covered but not anymore
Methods that could have a lower visibility
Avoid transforming an immutable type into a mutable one
Potentially dead Types
Class with no descendant should be sealed if possible
Constructor should not call a virtual methods
…
Not so much about keeping the code clean for the sake of it.
Code Rules
More often than not, a green rule that suddenly gets violated, sheds light on a non-trivial bug.
It is all about regression. It is not intrinsically about the rule violated……but about what happened recently, that provoked a
green code rule to be now violated.Demo!
The importance of Green Zone
On Clean Code Structure
What’s common about most of projects packaged into one large assembly?Nancy.dllNHibernate.dllMscorlib.dllSystem.dll
The code is completely entangled into namespaces dependencies cycle!
Common structure problem
The problem comes from the lack of definition of the notion of components in .NET.A component is a group of cohesive classes (cohesive in the
sense, one get used => all get used).There is no clear definitions about how to package these
classes other than the notion of .NET assembly (or VS project).
Consequences: Real-world applications are made of hundreds of .NET assemblies
Only one structure rule: Avoid cycles
But .NET assembly is a physical artefact:One assembly one physical fileNot fun to deploy hundreds of assembliesNot fun to reference dozens of assemblies of a library (and
maintain these referenced)
A component is a logical artefactFiner-grained than assembly => an assembly should
contain many componentsThis is why I advocate namespace to be the right
granularity to define component
Only one structure rule: Avoid cycles
250 namespaces spawned over 10 assemblies
Design of NDepend
For a public API, its practicaly impossible!For an application the code rule Avoid namespace
mutually dependent usually gives good hints about what to do, How?
Because developers usually know about high level and low level code.
For example, the rule say that mscorlib System namespace shouldn’t use any other namespace
Typically, resolving all namespaces mutually dependent will result into a well layered code structure.
How to get rid of cycles?
Two ways to get rid of an unwanted namespace dependency:Move type(s) from one namespace to anotherCreate an Inversion of Control by defining abstractions
(interfaces), in a new lower namespaceTry it! This is much cheaper to achieve than expected, and
getting rid of spaghettis is priceless! Because only code structure is touched (class def,
interfaces def, namespace def)Code flow (method bodies) is left untouched.
How to get rid of cycles?
Introduction to NDependContract, Unit Test and Code CoverageDesigning to make the UI testableDetect regressions early with Code RuleOn Code Structure
Questions?