-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#72 #69 #37 #14 #35 Redfine all Model classes to be readonly for public access. Use FinishUp method to calc useful properties and support for extended Multiplexing. #73
Conversation
im now working on a follow up to this. i enabled nullability for the dbcParserLib which makes it easier to see which properties could actually be null. I needed this for ExtendedMultiplexing as i adjusted the MultiplexingInfo to contain either the simple info or the complex info or none if the signal is not multiplexed. |
…dding support for extended multiplexing
With the last commit i added support for Extended Multiplexing in attempt to resolve #35 @EFeru you might wanna check the new extended multiplexing support. i suppose all information is there now but im not so sure about the structure. Also please check out the design for the model classes. I think the way it is now is atleast better as before but could probably still be improved im open to suggestens. Im also not to sure about these three functions: I decided to set the defautl only if a default exists which i think is not required and only if the value has not been set yet. However this could be changes to accomodate #70 i think at least. |
Im now considering this more or less done. If merged i would suggest doing this as a 2.0 Version as many things changed. As for the extended multiplexing support i cleaned that up but as for now the order is pretty much reversed. You can find the multiplexor for any multiplexed signal. But if you want the signals a multiplexor applies to you would need to search yourself. This is because the way extended multiplexing is defined. However you could add the multiplexed signals to a multiplexor after all the multiplexing was finalized. |
Hi, thanks for the effort. I would nevertheless try to split this one into multiple PRs. Cheers |
@Adhara3 im not sure if splitting this up is possible in a good way. Sure i could remove the framework stuff then probably the check would also go thorugh as UnitTest is running to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this PR is too wide and big. IMHO it should be splitted into smaller parts. Some are easy to do, like the target framework, other maybe more complex, but reviewing these massive and heterogeneous code changes is very hard and error prone.
Moreover, API changes and design changes should be handled with care and maybe discussed into a dedicated issue to reach a common agreement on the way to go (so that design, intentions and scenarios are shared upfront).
In particular please pay attention to the immutable/FinishUp relationship and to the string.Empty
vs null
meaning.
Otherwise the fixes sound reasonable, I have some minor style concerns (e.g. type checking in switch
statement) but put aside some possible performance and readability, I am not too strongly opinionated
Thanks for your effort
@@ -22,13 +22,14 @@ public SignalLineParser(IParseFailureObserver observer) | |||
|
|||
public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) | |||
{ | |||
if (line.TrimStart().StartsWith(SignalLineStarter) == false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change this? The reason why we set condition like this is readability. !
at the beginning may go unnoticed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of the changes were rider auto applying my companies code guidlines. Youll see that in most files i touched. For some stuff like the m_ naming i changed project specific rider rules so it wouldnt change that too ;)
@@ -7,10 +7,10 @@ public class Message | |||
{ | |||
public uint ID { get; internal set; } | |||
public bool IsExtID { get; internal set; } | |||
public string Name { get; internal set; } | |||
public string Name { get; internal set; } = string.Empty; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why you are setting all those to default string.Empty
. The idea is that a field that is null
has not been set while a field that is "empty" has been set to empty during parsing. null
and string.Empty
(or null
and new string[0]
) do have different meaning and I believe default should be not set (i.e. null
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was because i initially set the project to use nullablility which then markes a string as an issue if not defined nullable explicitly or initilaized.
overall i just dont like Nulls in a public API; expecially in this case here a name is also the key. name would definitly be something to set in the constructor.
i generally way wondering why mostly no constructors are used. When i would use property initializers instead like that i would still set them to init for set: public string Name {get;init;} which works with the property initializers aswell
DbcParserLib/Model/Dbc.cs
Outdated
public class Dbc | ||
{ | ||
public IEnumerable<Node> Nodes { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given all the comments already provided about the read only approach I would stress that IEnumerable<T>
is by design a readonly interface because it allows only reading (or iterating through iterator if you will), not editing (the collection).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another change due to code guidlines wich i normally follow. We only use IEnumarbale if we have functions that yield return. IEnumerable for me markes that the list can be build up while processing which is not the case here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, understood, sounds reasonable, event though how the collection is built, if lazy or eager, is a specific implementation detail of the implementing class.
{ | ||
Nodes = nodes; | ||
Messages = messages; | ||
EnvironmentVariables = environmentVariables; | ||
|
||
FinishUp(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do get the intent here and I agree that precalculating some unmutable values (e.g. IsMultiplexed) may help with performance. Nevertheless I think this is the wrong place (I would not do this stuff in the constructor) and that from a design point of view there should be a better solution also to solve the read only part.
dbc
being line by line requires mutability during parsing, this is why most objects were mutable. Properties are spread all over the dbc
file and as such I need to keep an object open to add/change/override properties while I go. If the final object should be immutable and optimized, then probably it's a different object. We then need a internal class ParsingMessage
(Message here is an example, but this should probably propagate to Signals too, if necessary) which is mutable (note the internal
keyword, not public API) and when the parsing is over then ParsingMessages
are converted into public class Message
which is optimized and unmutable.
The conversion is not Message
responsibility itself, but parsing engine's one (e.g. the parser knows whern it's done). Also, think about it, there is a sort of race condition. Message list is not ready/usable (IsMultiplexed
is not telling the truth) until the list itself is passed into a dbc constructor...
Sure, that this would bring to some sort of code duplication but would allow better responsibility separation keeping mutability where required by language (i.e. dbc) design, forcing unmutable and optimized properties where required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would still think that using the immutable stuff would be good for public api. In fact the first implementation i did was using that.
I just had some problems with that: It was unclear if the packer should work on immutable stuff or not. i didnt like the classes exposed in the public api all being named immutable. Thats why i would name the immutable stuff just signal,message aso. and the mutable stuff that is internal could just be named mutable or something like that.
I also think the public api shouldnt allow reference replacement as then you could just add unvalidated stuff. thats why i did all the setter to internal.
And just wrapping the internal stuff with a readonly for pulic access meant that you dont have to rebuild the dbc necessaryly when only adding small stuff. With the immutable stuff you would need to regenaret the immutable objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do agree that a read only dbc should be readonly.
|
||
public CustomProperty(CustomPropertyDefinition customPropertyDefinition) | ||
public CustomProperty(string name, CustomPropertyDataType dataType, ICustomPropertyDefinition propertyDefinition, IParseFailureObserver observer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pattern we were using is found in other projects like google's protobuf (see: oneof). I personally do not like runtime type checking. Moreover, CustomPropertyDataType
should be part of CustomPropertyDefinition
so if you really want to keep the interface and type checking, maybe CustomPropertyDataType
should be part of ICustomPropertyDefinition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the main problem for me with this approach is that you would always need a switch case when accessing the property which tends to be "long" code. In my code you coudl use the same thing for double,int and hex but for string and enum it still sucked ;)
the main thing that is annoying is that the property and the property defintion are seperated and you need this double checkign everywhere. I would consider it way nicer if you just have a custom proerty containing one of 5 objects which holds all the information in it.
Thanks for teh code review btw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Property and definition are separated beacause in the dbc they are separated, they are different tags.
This is a scenario where the parsing classes may require some trick but then for the API we can merge them together once the parsing is done
Cheers
A
All classes belonging to the model namespace are now readonly for public access. The immutable stuff was therefor removed. Internally all values can be written as before. Every class that profits from it now has a FinishUp method to calculate some usefull properties. Also the signals and the messages are now a dictionary which should resolve #14.
Some changes are breaking changes!!! (e.g dbc is now part of model namespace)
In the finish up methods i also update the MessageID for all signals in a message after applying the extendedId which should fix #72.
Also the IsMultiplexed value is also calculates once in this FinishUp method to resolve #37
The targetFramework was set to .net462, netstandard2.0 and .net8.0 to resolve #69
Also there are some other properties in the signal class like HasScaling which would help if extending the packling/unpacking functionalities for a next step after #67
Edit: Now should also resolve #35