-
Notifications
You must be signed in to change notification settings - Fork 41
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
Move various state computations from the UI to the NodeModel #434
base: main
Are you sure you want to change the base?
Conversation
Instead of having the UI compute the formatted version of verification progress using js, move this computation into the nodemodel; where it belongs. The UI is the UI, and the backend is the backend.
Instead of having the UI compute when the node is connected to the network, move this computation into the nodemodel; where it belongs. The UI is the UI, and the backend is the backend.
Instead of having the UI compute when the node is synced, move this computation into the nodemodel; where it belongs. The UI is the UI, and the backend is the backend.
Instead of having the UI construct the formatted version of the remaining sync time as well as computing when the node is estimating the remaining sync time using js, move this construction and computation into the nodemodel; where it belongs. The UI is the UI, and the backend is the backend.
This is not needed, and we cannot have any javascript functions.
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.
cr ACK
It looks much cleaner. Regarding the formatting, as discussed offline, I'd take it out from the NodeModel, I think it's not its responsibility and not related with business logic and the node itself, I'd create a separate object to make the formatting.
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 wanted to expand on my previous review and provide additional details, especially since we discussed in our call that more context and actionable feedback would be helpful.
Why this is important:
- Separation of concerns: By isolating formatting logic, NodeModel can focus on its primary responsibilities.
- Reusability: Formatting/conversion logic can be reused across modules or models, especially as we scale and introduce similar needs elsewhere.
- Improved collaboration: Keeping NodeModel lean ensures that team members working on different aspects of the model don't block each other over formatting corrections or tweaks, enabling smoother parallel development.
Counterpoint: While we were advised to avoid generalizing for single-use cases, I believe this is an exception for two reasons:
- Formatting/conversion needs are likely to emerge in multiple places over time.
- Even if it's only a single case today, the reduction in potential merge conflicts and enhanced modularity justify the small upfront investment.
Specific Suggestions:
-
Refactor the large setFormattedRemainingSyncTime function into a TimeFormatter class or similar utility.
Snippet code sample.
// NodeFormatter.h #ifndef NODEFORMATTER_H #define NODEFORMATTER_H #include <QString> class NodeFormatter { public: static QString formatRemainingSyncTime(int new_time, bool& estimating); static QString formatVerificationProgress(double progress); }; #endif // NODEFORMATTER_H
Modify NodeModel to delegate formatting logic to NodeFormatter:
#include "NodeFormatter.h" // Existing methods void NodeModel::setFormattedRemainingSyncTime(int new_time) { bool estimating = false; m_formatted_remaining_sync_time = NodeFormatter::formatRemainingSyncTime(new_time, estimating); setEstimatingSyncTime(estimating); } void NodeModel::setFormattedVerificationProgress(double new_progress) { m_formatted_verification_progress = NodeFormatter::formatVerificationProgress(new_progress); }
-
Use a handler pattern to simplify the chain of if conditions.
Snippet code sample.
class TimeFormatterHandler { public: virtual ~TimeFormatterHandler() = default; virtual bool canHandle(int new_time) const = 0; virtual QString handle(int new_time, bool& estimating) const = 0; }; // Example: WeeksHandler class WeeksHandler : public TimeFormatterHandler { public: bool canHandle(int new_time) const override { return new_time >= 10080 * 60000; } QString handle(int new_time, bool& estimating) const override { int weeks = new_time / (10080 * 60000); estimating = false; return QObject::tr("~%1 %2 left").arg(weeks).arg(weeks == 1 ? QObject::tr("week") : QObject::tr("weeks")); } }; // Add other handlers: DaysHandler, HoursHandler, etc. QString NodeFormatter::formatRemainingSyncTime(int new_time, bool& estimating) { static const std::vector<std::unique_ptr<TimeFormatterHandler>> handlers = { std::make_unique<WeeksHandler>(), // Add other handlers in priority order }; for (const auto& handler : handlers) { if (handler->canHandle(new_time)) { return handler->handle(new_time, estimating); } } estimating = true; return QObject::tr("Estimating"); }
To enforce clear seperation between the UI and the backend, as well as to begin the removal of any js functions, this shifts the following string construction and state computations from the UI into the NodeModel itself (which is where it belongs)
These strings and states are now available and reusable wherever they may be needed, instead of needing to be computed by every file which needs that information.
As a rule, the UI should have no computation of any sorts outside of things related to UI specific ideas such as color or sizing; any other computation needs to be strictly done in our backend.