Recommend improve code smells and add XML docs
astrohart opened this issue · comments
Context
When utilizing the code in my own project, there is scant XML documentation to help me remember how to utilize the various properties, methods, objects, and fields. etc. As a result, when programming this code base, I find myself having to flip constantly between my code and the API docs.
Also, there are a number of code smells that my ReSharper add-in is showing me:
- Types need to be moved to their own
.cs
files; - XML doc comments should be added to align with the API docs, so as to guide developers who are programming to this library through how to use it;
- Redundant use of the
this
when it's not necessary. - Dependencies passed to constructors (and the properties/fields that store references to them) should be typed with the interface that that object implements, not the concrete type.
Alternatives
Would like to submit a PR with the issues above addressed.
- Moving the types to matching files (so there is just one class/interface per file) makes the code easier to navigate for contributors.
- XML doc comments copied and pasted from the API documentation show up as ToolTips when clients of the NuGet package are programming to it, helping remind them of how the API needs them to use the library in order to then cut down on client edit/build/debug cycles.
- Remove use of needless
this.
etc to clean up the code and make it easier to deal with when contributing. - The last bullet above -- passing dependencies using interfaces and making properties and fields be typed by interface etc. -- allows for better integration with IoC frameworks, which is especially important to those clients of the library who are writing mobile apps.
Another argument for the last bullet above is that concerns are separated in this event, and then the code is more modular and testable, and can easily be expanded to incorporate new code without breaking the existing stuff.
Has the feature been requested before?
To my knowledge, no.
If the feature request is approved, would you be willing to submit a PR?
Absolutely. I enjoy doing this sort of thing with code bases. My thought process is that it would not be too difficult, since I have ReSharper Ultimate VS extension which makes the refactoring go by fast. Plus -- I am actively programming to this library, so I would like to see more XML doc tooltips pop up when I am utilizing the objects that it provides.
Types need to be moved to their own .cs files;
Moving the types to matching files (so there is just one class/interface per file) makes the code easier to navigate for contributors.
Depends, what types are you looking to move? Interfaces like the example below and implementation types stay in the same file:
XML doc comments should be added to align with the API docs, so as to guide developers who are programming to this library through how to use it;
Please give an example where the XML docs in the library differ from the API docs so I know what you're talking about.
Remove use of needless
this.
etc to clean up the code and make it easier to deal with when contributing.
No. Actually, I'd probably be more willing to accept a PR that increases the usage of this.
(for field members) where I was lazy. IIRC, this is a tunable setting in ReSharper. I think the usage of this.
is mostly a C# style choice rather than something that should be taken as gospel from a default setting in ReSharper.
passing dependencies using interfaces and making properties and fields be typed by interface etc. -- allows for better integration with IoC frameworks, which is especially important to those clients of the library who are writing mobile apps.
Another argument for the last bullet above is that concerns are separated in this event, and then the code is more modular and testable, and can easily be expanded to incorporate new code without breaking the existing stuff.
It depends; increased abstraction comes at a cost of increased complexity. I'd rather not blindly abstract everything behind an interface. For me, keeping code "simple" and "easy to use" carry a higher priority than abstracting behind interfaces just-because. This extends more from a PDD approach more than anything else:
This will have to occur on a case-by-case basis. You'll have to give explicit examples of what things you think should be abstracted behind an interface.
Hi @bchavez
Introduction
Believe me, when I say, I am not trying to sound undiplomatic, but I know what I am talking about, and I am just brutally honest. I understand that you've done a lot of work on this code, and I love the library as a whole -- it powers my awesome bot I am making. And it just frigging works. So, kudos. Now, let me tear apart your arguments in the previous post to shreds because I like your library, and I sincerely want to help you see how it can be even better.
My background
You have some good points, but overall I think you might be wrong on several fronts; I am also the voice of experience, so hear me out before you get mad at me :-) I have 26 years of programming experience in all sorts of languages, plus a Ph.D. in Astrophysics from the University of California, Irvine. I know my craft.
Voice of experience
I've done a HUGE amount of software development, and I am widely published. I wrote books on C# more years ago than I care to remember (they all went unfinished because I happened to be in college at the time, and so it was either finish the manuscript or do my homework). I have also published 50+ articles from over a span of 3 years in C++ computer programming and software development. I am, like, crazy into code smells, and I can see them from a million miles away partly because I've made many of the same mistakes in the past of my career. And I don't want that to happen to your wonderful code.
Please don't hurt me!
Sorry again. I know that saying to people, "I am better than you," is not a way to win an argument -- and I am truly trying my very best not to come off that way. I just wanted to outline my background, credentials, and experience because I want to invoke those credentials' intellectual authority and that experience to help you understand. I am waving my credentials and experience around to get you to sit up and take notice that I am not just coming from a "religious war" perspective. I sincerely hope that that would be the result.
Time to crack knuckles
Okay, let's unpack what you've told me, and I hope I can convince you.
The main point of my whole issue
The upshot of why I want to engage in this refactoring is, the way your code is now, it makes your code more difficult to contribute to as a GitHub contributor and also harder to navigate through whilst debugging/testing my own stuff built on top of it (through the PDB decompiler built into Visual Studio Debugger). Lots of scrolling. The text is just so crowded together on the screen; thus, I really have had a difficult time piecing out where the current instruction pointer is, even though Visual Studio highlights it.
Point 1: Why types should be in separate files
Depends, what types are you looking to move? Interfaces like the example below and implementation types stay in the same file:
First off, let me see that I can see the point of why you have grouped types in files as you do. It's actually pretty Pythonic when you think about it -- each .cs
file is kind of like a Python "package," so to speak. You want each .cs
file to group the types that are all related to each other, and all pretty much do the same thing. But, there are advantages and disadvantages to your approach. I hope I can convince you that the disadvantages outweigh the advantages.
Advantages of your approach
The primary advantage I see of grouping related types in the same .cs
file is semantic grouping -- meaning, we are categorizing and mind-mapping the code because all the types that naturally relate with one other are all in the same file. You have an interface, the class(es) that inherit it, and so on, all down through the type hierarchy, all together in one file.
Disadvantage of your approach
The primary disadvantage of this approach, however, is that it makes the technical debt increase exponentially.
Source files are hugely long
The more types are packed into the same file, the more monolithic (longer) they are, scrolling wise. It is a barrier to productivity to be scrolling all the darn day through some huge, insanely long .cs
file (like the .cs
files for the Models, for instance).
Debugging is hard when testing because of the scrolling (even if VS jumps you to the next instruction)
And it's also not so useful when debugging and stepping into the package from the VS debugger (who decompiles that part of the package back into a monolithic .cs
file if that is how it was written in the first place). Really not conducive to quickly see the spot in the code where I am at.
Maintenance becomes more and more difficult with increasing .cs
file length
Maintenance of the code depends on quickly finding the source of an error or bug and fixing it. This is harder, IMHO, to do when the .cs
files are a million miles long.
Hard to refactor with scripts
In any large enterprise-level project, there is sometimes the need to refactor by actually opening up LINQpad or some other C# code scratchpad and iterate over the filesystem, making changes to the text of the files through pattern-matching. It's easier to do this if the files all follow uniform conventions, such as the file's name being the same as the sole type declared in it. Then your refactor script can simplify assumptions about the contents of the file, thus reducing the need for more parsing cycles. On this end, it may make VS work slightly faster since there is less parsing needed (for example, I'd need first to find all the lines containing public class
in one of your .cs
files, say, with my scripts, and then iterate through those to find the class I want. If the file name itself matches the class' or interface's name, then I can assume that for file Foo.cs
, there is definitely a public class Foo
line in it, and it is the only one.
Caveats
I will admit that if types are in separate files, every time you step from one to the other, you are spending time during debugging, making VS open one tab after another as you jump from one .cs
file to the other.
However, in my opinion, it's worth it not to have to scroll some huge monolith. And the time to open tabs is negligible since there are RAM and CPU to spare on most modern dev boxes.
Discussion
IMHO the disadvantages discussed above outweigh the advantages of your approach. IMHO, every single type declared, be it an interface,
struct
, delegate
, class
, or enum
(among others), must be in its own file. It also enhances the ability to do XML docs (see below). ESPECIALLY the POCOs in Models
. One of the things I am an expert in is writing .tt
files. These work the best if the types are in separate files. You can use these to create scripts that consume a data source and generate code.
While this is optional in C#, Java requires it. I am not saying C# is better than Java by any means. I am just saying there is an advantage in only having one type per file. In partial types -- I like to put all the properties in one file and the methods in another.
Another upshot, I think, of this approach is, just by looking at the list of .cs
files in Solution Explorer, you can tell immediately what types are in the code. It makes maintaining it so much easier when you can jump right to the .cs
file you need.
Also, my approach allows for easier creation of refactoring scripts, e.g., with LINQpad. It simplifies the design of refactoring algorithm when conventions are used such as this one since it simplifies the assumptions you otherwise would have to make (for example, if I am writing a script to change all the names of classes where the names match some criteria, such as starts with 'Coinbase').
We could have an entirely religious discussion and go back and forth a hundred times with each of us pointing out additional advantages and disadvantages, which might last an eternity. Or you could consider allowing me to perform the refactoring.
See above. Seriously -- with ReSharper, it is just a single menu click, and then it's done. I ask that you please allow me to perform this change. And I especially suggest creating a separate issue in GitHub just for this change since it will be a larger commit. This way, we can do a PR just for this.
Point 2: Add XML doc comments to EVERYTHING
Please give an example where the XML docs in the library differ from the API docs so I know what you're talking about.
It's hard to give specific examples at this time because that would require going through lots of code. It's not just that -- see below:
Bring the code to the masses, at every knowledge level
For example, while what the ICoinbaseClient
interface is, it is blindingly obvious to experts such as ourselves, but not to the novice developer. I am an educator. I am always trying to bring STEM to the masses and lower it down to where the largest amount of people can use science and technology to benefit humankind.
IDE tooltips while writing bots! Yay!!
With more XML docs more IDE tooltips will pop up explaining what objects are what and why they are there in the first place.
Generate Markdown from the XML docs and add those to your REPO, so there is the ready-made online documentation for the code objects
Moreover, tools such as the Vsxmd
NuGet package can then be run on the solution to -- poof!_ produce Markdown docs of your code on each build. You can then include them in the repo for when people suck the package down and refer to the GitHub markdown page for more info. You can make a Docs
subfolder of the project root and include them all there. NOTE: I am not necessarily advocating using Vsxmd
per se; I am just holding it up as an example. Plus, I am sure you also have extensive docs already, which is good!
Document the models, not just what the properties are, but explain how they are to be employed in usage scenarios
Moreover, the Models
classes must all have XML docs. I've spent the past 18 months writing what I think is a pretty sophisticated Coinbase bot, and, during the course of doing so, I have acquired a profound understanding ( I think ) of the JSON resources and such that Coinbase API returns.
What Coinbase returns as JSON resources are usable only in certain ways and there are some subtle caveats -- explain these in XML docs
I've also hit snags while using the models, and, for example, your Buy
and Sell
objects are not documented with XML docs, especially XML docs that explain the quirks of the JSON resources (such as, did you know you can use the ExtraJson
in the Sell
resource to dig deep and determine things that can help AIs to be written against Coinabase, such as the cost basis (if I am even using that term right)). It's not necessarily that the API docs are wrong, but I know about how to utilize the library for this scenario or that other scenario IRL.
Add remarks to elaborate
XML docs are not just for saying This is the interface for the Accounts endpoint
but more for the <remarks>...</remarks>
sections that can be added that explain, On Coinbase, when it says Account, it really is talking about one of the individual little wallets, such as the AAVE account meaning the AAVE wallet. Really, what Coinbase calls an Account is really a Wallet. If Coinbase were a stock-trading platform, then the Account resource tells you how much MSFT you have or how much AMZN you have, plus all the transactions that are related to it.
The main thrust of why copious XML docs are important
At the end of the day, it's not just about copy and pasting the API docs into XML doc comments. It's about (a) describing how everything interrelates on the language as well as conceptual level, and (b) putting my learnings from really having beaten the API just to death so that other users of the library may benefit from my R&D, which I am more than happy to do.
Also, there's this...
BTW - you would not believe the number of devs out there that don't know what a C# interface
is for and why we care. I do, and I can explain that in the XML docs.
Discussion
I want to invite you to think about letting me do it. I would propose to summarize each thing in the <summary>...</summary>
and also include <param>
and <exception cref>
and <returns>
blocks, and also add <remarks>
to explain how the library is to be utilized against the Coinbase platform. I have written a massive amount of bot code against your library and unit tested the living sh*t out of it. I would like to have the opportunity to pour into the XML docs the learnings from seeing how the data sent back by the API (as well as how to properly fill in the properties of a Buy
resource to make a purchase -- the API docs make it confusing and the property names from the API are not as intuitive as they could be).
We aren't just trying to copy and paste the API docs into the XML docs (esp. because the API docs are terse and poorly written, IMHO), but we are trying to explain to the boots-on-the-ground, developer-sitting-at-home-trying-to-write-a-bot, here's how this thing works. My approach would be to make the XML docs excruciatingly detailed so they can be really "learned from" and harnessed by not just experts but also by novice developers. Tens of thousands of people use your library (as can be seen from the NuGet Package Manager's download figures). Don't you want to have it be as thoroughly-documented as possible?
No. Actually, I'd probably be more willing to accept a PR that increases the usage of this.
I strongly beg to differ. We need _ fewer_ usages of this
, not more. The fewer, the better.
Point 3: Limit usage of this
to only that which is absolutely necessary
Not just taking ReSharper as gospel
As much as you may think I am some naive dweeb, I am not. I actually am a strong proponent that 99% of the default ReSharper inspections are spot-on. This comes from my deep programming experience -- I've seen the good, the bad, and the ugly.
Some might argue this
should be used to refer to fields when they're named the same as variables.
First off, I strongly believe in the convention of naming fields with a leading underscore. Secondly, the C# language, even that which shipped with .NET 4.6.1, is so powerful that we can get around some of these issus, thus rendering this discussion moot.
- (a) do not name fields the same as variables. A leading underscore is the way to go.
- (b) Fields are out of fashion these days in favor of auto-properties and computed properties and compute-once-and-store properties.
Use auto-properties instead of fields unless you absolutely have to.
You can make properties private
if you really want to. The advantage to the above approach is that then you do not have a debugger view where you have two rows of each data item in the pop-up for a class variable that appears when you hover the mouse over it while debugging. One for the field, and the other for the property. The idea is that auto-properties (a) make the compiler do the work of declaring backing fields, and (b) then the debugger visualizer is so much cleaner and easier to use. A good use for a field is to access an injected dependency, in which case, I say leave the field. That is about all they are good for nowadays.
Use get-only auto-properties instead of { get; private set; }
...unless you need to set the value of the property in the code of the class in somewhere other than the construtor. But if the property is merely being initialized by a parameter being passed to the constructor, then forget it.
Use compute-once-and-store properties to initialize collections.
I am not sure if the code already does this, but I strongly advocate doing this whenever possible.
For example, consider the following code:
public class MyClass
{
public List<Balloon> Balloons { get; }
public MyClass()
{
Balloons = new List<Balloons>();
}
}
This code can now be changed to:
public class MyClass
{
public List<Balloon> Balloons { get; } = new List<Balloons>();
}
See? Nice and compact. You probably already knew about this but just wanted to put it out there. The =
after the property declaration just specifies a one-time initialization upon class construction.
When injecting a dependency in the constructor, use the most abstract way of doing so...for loose coupling
Every time you pass a reference to an object into a parameter, do so by declaring the parameter to be of the type of interface that that object implements. This makes mocking things easier and doing unit testing with fakes possible. It also separates concerns. When you change the concrete implementation the code that uses that object does not break. A little upfront refactoring work now makes for less time to maintain down the road. Believe me, it's worth the effort.
I do PDD - pain driven development
I've seen the links you sent me. They basically advocate just smashing your solution together -- to hell with robustness, modularity, or such -- until you hit a pain point. Once pain is experienced, then fix it. There are plusses and minuses, but mostly minuses to that. I see pain as an inevitability, but PDD Is just delaying the inevitable. It's better to write the code modular and robust upfront I think. Then, when pain is eventually experienced, it's just a small fix instead of a massive refactoring.
Let me give you an example from my experience. I used to write a software system that used that approach. However, when the pain finally came, it was so massive that it basically caused me to have to stop maintaining it...the code just got so ugly and gnarled that it could no longer be refactored to add additional functionality without making a TON of changes elsewhere. The more and more sophisticated the system got, the longer it took to make even a small change.
This codebase is on the cusp of that. Let me help. Look, I am just going to fork it anyway and then create a PR...how about letting me, and then letting the PR show you what was done? See if you do not agree then.
Point 4 Have all types implement an interface and only pass dependencies via interfaces
It depends; increased abstraction comes at a cost of increased complexity.
First of all, I want to say, this is the one statement you made I 100% agree with. For example, you do NOT want to extract interfaces from pure POCOs...no. Bad news. Then the classes who operate on those POCOs have to implement generic interfaces that have covariant and contravariant type parameters, and that's not compatible with IList
and friends, anyhow.
So, the thrust is: I am not advocating inheriting EVERY Class from an interface, but doing it for the RIGHT classes is the key. I see that has mostly been done here...but it does not hurt to do a review-and-refine process.
'Service' classes and 'data' classes
In your project, I already see that this distinction has been made, but in most C# projects, the code can in principle always be refactored so that there are two types of objects: "service" classes and "data" classes (i.e., POCOs.).
Service classes can have a mix of properties, fields, and methods but generally perform operations and the Data classes haul data around and more or less consist only of POCOs. Only the Service style classes need to implement interfaces and be passed in via interfaces, as do collections (do not take a parameter of type List
but rather IList
or IEnumerable
, ditto for return type).
The power of C# interfaces is implementation hiding. If you limit users of the library to more or less having to solely deal with interfaces and factories, then you can decouple the user's code from your code. Then, if you need to overhaul the implementation, you can do so without having to worry if you are breaking the code of the NuGet package's users. I used to do a lot of work with Component Object Model (COM) programming and this was an essential aspect of that style of programming. Today, it's even more essential to make code as loosely-coupled as possible and separate concerns as far apart as can be. This serves to limit technical debt, making switching out implementation strategies less impactful on clients of the code and decrease technical debt.
Final thoughts
So, that's my two cents. I am all for "if it ain't broke, don't fix it," as you said, but at some point, when the code becomes a big heap, then the pain finally experienced is like throwing out your back as opposed to just stubbing your toe. We're not there yet, but this codebase as written is a ticking time bomb. The goal of performing the work I am suggesting is to ultimately reduce technical debt going forward.
I don't disagree necessarily with anything you said above; however, a significant point that we have to realize is that I have to maintain this code and this is not an enterprise product. Ultimately, the burden of maintaining this code falls on me. Whatever style of programing, idioms, or practices I employ here is for my benefit. I know this doesn't sound diplomatic, but this is the way it is.
There are fundamental reasons why the code is structured the way it is.
Let's just take one quick example, the Model
classes structured in one monolithic .cs
file. Why? Because Coinbase's models can change; and when they do change, sometimes it's a simple change; other times a single change should have a cascading effect. I have to be able to quickly CTRL+F, and spot check various parts of the entire hierarchy model to see if I can spot any potential issues QUICKLY; I don't want to spend my time opening up multiple *.cs files in my editor and CTRL+F on each file, and is more error-prone. I'd much rather scan a large monolithic file for what needs to change than scanning multiple files across the disk. I need to see the entire hierarchy, in full context, without having to piece it together from multiple files.
Again, the way things are set up is optimally geared for me to maintain this project. With respect, there are no compromises here. If you want to contribute to this project you have to agree with these terms.
Re: XML Docs
Add remarks to elaborate
XML docs are not just for saying This is the interface for the Accounts endpoint but more for the<remarks>...</remarks>
sections that can be added that explain, On Coinbase, when it says Account, it really is talking about one of the individual little wallets, such as the AAVE account meaning the AAVE wallet. Really, what Coinbase calls an Account is really a Wallet. If Coinbase were a stock-trading platform, then the Account resource tells you how much MSFT you have or how much AMZN you have, plus all the transactions that are related to it.
I want to invite you to think about letting me do it. I would propose to summarize each thing in the<summary>...</summary>
and also include<param>
and<exception cref>
and<returns>
blocks, and also add<remarks>
to explain how the library is to be utilized against the Coinbase platform. I have written a massive amount of bot code against your library and unit tested the living sh*t out of it. I would like to have the opportunity to pour into the XML docs the learnings from seeing how the data sent back by the API (as well as how to properly fill in the properties of a Buy resource to make a purchase -- the API docs make it confusing and the property names from the API are not as intuitive as they could be).
I'm generally okay with this. If you want to contribute I'd suggest starting small. Pick one API method to add XML docs and demonstrate what you'd like to do. Submit a small PR and we can review it. Do not add XML docs to everything and submit a massive PR. PRs should be broken up into the smallest sizes so they are easily digestible and can easily be reviewed.
Re: C# coding style
Again, please leave the C# this.
and all coding styles as-is.
You do know you can get an extension that is not ReSharper but lets you press CTRL+T and navigate to anything in the entire solution just like that? No need to open a file and ctrl+f it.
Anyhow, given the above, your the boss of your own code and what you say, goes. I understand, acknowledge, accept, and agree to your terms and conditions.
I love your library and it’s totally awesome. So I’m not really going to do anything for right now because the code works anyway. I’m just going to continue to bump up against it in my bot and if I run into something glaring that needs to be fixed to actually make the lib work, then I’ll raise that as an issue and discuss.
The stuff that you approved — let me do it if I have spare cycles. Otherwise, with that I’ll close this issue.
I am however HUGELY appreciative that you heard me out and thoughtfully considered my suggestions :-). A big “thank you” for that.