Skip to content
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

Make the code more understandable #236

Closed
michaeljones opened this issue Jan 15, 2016 · 7 comments
Closed

Make the code more understandable #236

michaeljones opened this issue Jan 15, 2016 · 7 comments
Assignees
Labels
duplicate Duplicate issue

Comments

@michaeljones
Copy link
Collaborator

I have come to accept that the code is unnecessarily complex to understand. I now realise it could be much more readable and approachable. I have defended it in the past but it must be possible to make it better.

I hope that by improving it we might get more contributions from people who are interested but currently turned off by the state of the code. This would definitely help the project as I don't find much time to work on it and move it forward.

Broadly, I will try to reduce the number of factories being used and have less dependency injection. The dependency injection was mostly designed to make the code more testable but I have never written tests to take advantage of that so it is just a cost to readability at the moment.

I would love some input to help identify particularly difficult parts of the code or to propose improvements. I am keen to improve it now but not entirely sure how to start. But I will try :)

@vitaut
Copy link
Contributor

vitaut commented Jan 15, 2016

For me the most difficult part was DoxygenToRstRendererFactoryCreatorConstructor. I am still not sure I understand how it works =):

    math_nodes = collections.namedtuple("MathNodes", ["displaymath"])
    math_nodes.displaymath = sphinx.ext.mathbase.displaymath
    node_factory = NodeFactory(docutils.nodes, sphinx.addnodes, math_nodes)

    rst_content_creator = RstContentCreator(ViewList, textwrap.dedent)
    renderer_factory_creator_constructor = DoxygenToRstRendererFactoryCreatorConstructor(
        node_factory,
        parser_factory,
        DomainDirectiveFactory,
        rst_content_creator
        )

@michaeljones
Copy link
Collaborator Author

Ha, ridiculous name eh? I am very happy to try to get rid of it. Though that class itself does very little so is it more than whole DoxygenToRstRenderer stack that you'd like to see improved or you just want to be free of that one class?

@vitaut
Copy link
Contributor

vitaut commented Jan 15, 2016

Name kind of makes sense if you are familiar with musical terminology like hemidemisemiquavers =). I'd go with simplifying this chain of factories first if possible. Currently it looks like all the callers use the same pattern:

        renderer_factory_creator = self.renderer_factory_creator_constructor.create_factory_creator(
            project_info,
            self.state.document,
            target_handler
            )

        try:
            renderer_factory = renderer_factory_creator.create_factory(
                node_stack,
                self.state,
                self.state.document,
                filter_,
                target_handler,
                )
        except ParserError as e:
            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
                                       self.lineno, True)
        except FileIOError as e:
            return format_parser_error("doxygenclass", e.error, e.filename, self.state, self.lineno)

        context = RenderContext(node_stack, mask_factory, directive_args)
        object_renderer = renderer_factory.create_renderer(context)

Since all three levels of creation happen together, perhaps this chain of factories can be collapsed.

@michaeljones
Copy link
Collaborator Author

That's interesting. I'll give it some thought. I've made some progress getting rid of some stuff. I'll push in a bit I hope.

michaeljones pushed a commit that referenced this issue Jan 15, 2016
Tried to refactor it. Turns out it wasn't being used.

For #236
michaeljones pushed a commit that referenced this issue Jan 15, 2016
I really got lost along the way somewhere.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
Import and use ViewList & textwrap directly instead of going through a
layer of indirection. I initially did stuff like this out of a belief
that it made it more testable as it was easy to mock out the stuff being
passed in. However I haven't written any tests and even if I did, I
wouldn't mock 'dedent'.

For #236
michaeljones pushed a commit that referenced this issue Jan 15, 2016
Not sure why we have it. Doesn't appear to be used again.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
Not at the top followed by passing it all the way down to where it is
needed.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
Instead of creating it at the top and passing it all the way down.

This does mean that we have a lot of NodeFactory instances but they are
light weight so nothing to be worried about.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
Instead of passing it down from the top. Reduces the need for the
intermediate factories which we'll hopefully eliminate at some point.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
I assume it has been fixed on the Sphinx side a long time ago. It was
reported a long time ago anyway.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
Like the others. Might as well have it close to where it is used.
Unlikely to want to replace it for testing purposes and it avoid all the
intermediate passing around.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
To clean them up as they don't seem to be being used.

For #236.
michaeljones pushed a commit that referenced this issue Jan 15, 2016
It never did much and we've already removed most of the things it helped
pass on. Here we remove it completely and inline the construction of
the subfactory directly as we always have the parser_factory when we
want to build it.

For #236.
@vitaut
Copy link
Contributor

vitaut commented Mar 30, 2016

@michaeljones, great job with refactoring! I have another idea that I want to discuss with you before starting any development.

The idea is to apply the visitor pattern to renderers. To be more specific, I suggest introducing a Doxygen node visitor class:

class DoxygenVisitor:
  def visit_function(self, node):
    pass # called on memberdef node of function kind

  def visit_typedef(self, node):
    pass # called on memberdef node of typedef kind

  ...

and using it to implement the Sphinx/RST renderer:

class SphinxRenderer(DoxygenVisitor):
  def visit_function(self, node):
    # renders Doxygen function node using Sphinx function directive from the current domain

  def visit_typedef(self, node):
    ...

I think this will simplify the code because all the shared state that currently needs to be passed through *Renderer objects will reside in a single place, SphinxRenderer. In fact, the node to render seems to be the only non-shared state and it will be passed as an argument to visit methods.

DoxygenToRstRendererFactory will become unnecessary (although some of it code will probably move to DoxygenVisitor) because there will be one renderer class. At the same time it will still be possible to modify the rendering behavior by subclassing SphinxRenderer and overriding its methods.

This could also improve performance because we won't be creating many *Renderer objects.

What do you think?

@michaeljones
Copy link
Collaborator Author

That sounds really promising. I'd be really interested to see that. I'm happy with the little progress I made trying to cull stuff and clean up a bit, but the code is so familiar and such a representation of how I've been thinking about Breathe throughout the project that I really struggle to imagine fundamentally different ways to approach it. We definitely need to shake it up a bit to have a better go at making it a more approachable code base.

Please go for it and let me know if I can help. Though I suspect such things are tricky to share. Happy to help if I can though.

@vermeeren
Copy link
Collaborator

I think it's safe to say that refactoring is completed at this point.

I do agree the code should still be made more understandable, that will be handled in #357.

Closing this for now as duplicate of #357.

vermeeren pushed a commit that referenced this issue Jun 4, 2018
Tried to refactor it. Turns out it wasn't being used.

For #236
vermeeren pushed a commit that referenced this issue Jun 4, 2018
I really got lost along the way somewhere.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
Import and use ViewList & textwrap directly instead of going through a
layer of indirection. I initially did stuff like this out of a belief
that it made it more testable as it was easy to mock out the stuff being
passed in. However I haven't written any tests and even if I did, I
wouldn't mock 'dedent'.

For #236
vermeeren pushed a commit that referenced this issue Jun 4, 2018
Not sure why we have it. Doesn't appear to be used again.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
Not at the top followed by passing it all the way down to where it is
needed.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
Instead of creating it at the top and passing it all the way down.

This does mean that we have a lot of NodeFactory instances but they are
light weight so nothing to be worried about.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
Instead of passing it down from the top. Reduces the need for the
intermediate factories which we'll hopefully eliminate at some point.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
I assume it has been fixed on the Sphinx side a long time ago. It was
reported a long time ago anyway.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
Like the others. Might as well have it close to where it is used.
Unlikely to want to replace it for testing purposes and it avoid all the
intermediate passing around.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
To clean them up as they don't seem to be being used.

For #236.
vermeeren pushed a commit that referenced this issue Jun 4, 2018
It never did much and we've already removed most of the things it helped
pass on. Here we remove it completely and inline the construction of
the subfactory directly as we always have the parser_factory when we
want to build it.

For #236.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate Duplicate issue
Projects
None yet
Development

No branches or pull requests

3 participants