Calculate subtotals and totals from a form with many decimal values
$begingroup$
I've got 17 currency fields on a form and need to do some calculations with their values. I wrote this method some time ago and have come back to try and refactor it to make it more efficient and/or more readable. Could I have some pointers please?
Note: The variables appended with d
are the decimals used to hold the field values, their counterparts without the d
are the fields themselves. I am aware that variables could do with renaming to make more sense. Hopefully it's fairly clear what this is doing by looking at the comments.
private void getTotals() {
//declarations of decimal variables
decimal curPurc1d; decimal curPurc2d; decimal curPurc3d; decimal curPurc4d; //purItemxCost
decimal curItem1Totd; decimal curItem2Totd; decimal curItem3Totd; decimal curItem4Totd; //curItemxTot
decimal LessItem1Costd; decimal LessItem2Costd; decimal LessItem3Costd; decimal LessItem4Costd; decimal LessItem5Costd; //LessItemxCost
decimal ditem1Cost = 0; decimal ditem2Cost = 0; decimal ditem3Cost = 0; decimal ditem4Cost = 0; //Full cost of items
//Check if we have some valid numbers, stop if we don't
if ( !decimal.TryParse(curPurc1.Value.ToString(), out curPurc1d)
|| !decimal.TryParse(curPurc2.Value.ToString(), out curPurc2d)
|| !decimal.TryParse(curPurc3.Value.ToString(), out curPurc3d)
|| !decimal.TryParse(curPurc4.Value.ToString(), out curPurc4d)
|| !decimal.TryParse(curItem1Tot.Value.ToString(), out curItem1Totd)
|| !decimal.TryParse(curItem2Tot.Value.ToString(), out curItem2Totd)
|| !decimal.TryParse(curItem3Tot.Value.ToString(), out curItem3Totd)
|| !decimal.TryParse(curItem4Tot.Value.ToString(), out curItem4Totd)
|| !decimal.TryParse(LessItem1Cost.Value.ToString(), out LessItem1Costd)
|| !decimal.TryParse(LessItem2Cost.Value.ToString(), out LessItem2Costd)
|| !decimal.TryParse(LessItem3Cost.Value.ToString(), out LessItem3Costd)
|| !decimal.TryParse(LessItem4Cost.Value.ToString(), out LessItem4Costd)
|| !decimal.TryParse(LessItem5Cost.Value.ToString(), out LessItem5Costd)
)
return;
//For each of the 4 'items', try to get the value as a decimal, but only if the related checkbox is checked.
if (Item1RateYN.Checked)
{
decimal.TryParse(curItem1Cost.Value.ToString(), out ditem1Cost);
}
if (Item2RateYN.Checked)
{
decimal.TryParse(curItem2Cost.Value.ToString(), out ditem2Cost);
}
if (Item3RateYN.Checked)
{
decimal.TryParse(curItem3Cost.Value.ToString(), out ditem3Cost);
}
if (Item4RateYN.Checked)
{
decimal.TryParse(curItem4Cost.Value.ToString(), out ditem3Cost);
}
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1d + curPurc2d + curPurc3d + curPurc4d + curItem1Totd + curItem2Totd + curItem3Totd + curItem4Totd
+ ditem1Cost + ditem2Cost + ditem3Cost + ditem4Cost;
subtotal.Value = subtotals;
//Get total minus the cost of the property (curPurc1d)
costPlusSubTotal.Value = subtotals - curPurc1d;
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Costd + LessItem2Costd + LessItem3Costd + LessItem4Costd + LessItem5Costd;
totalLess.Value = lessTotals;
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//set the final figure into the relevant field
balanceDueTot.Value = total;
}
c# winforms
$endgroup$
add a comment |
$begingroup$
I've got 17 currency fields on a form and need to do some calculations with their values. I wrote this method some time ago and have come back to try and refactor it to make it more efficient and/or more readable. Could I have some pointers please?
Note: The variables appended with d
are the decimals used to hold the field values, their counterparts without the d
are the fields themselves. I am aware that variables could do with renaming to make more sense. Hopefully it's fairly clear what this is doing by looking at the comments.
private void getTotals() {
//declarations of decimal variables
decimal curPurc1d; decimal curPurc2d; decimal curPurc3d; decimal curPurc4d; //purItemxCost
decimal curItem1Totd; decimal curItem2Totd; decimal curItem3Totd; decimal curItem4Totd; //curItemxTot
decimal LessItem1Costd; decimal LessItem2Costd; decimal LessItem3Costd; decimal LessItem4Costd; decimal LessItem5Costd; //LessItemxCost
decimal ditem1Cost = 0; decimal ditem2Cost = 0; decimal ditem3Cost = 0; decimal ditem4Cost = 0; //Full cost of items
//Check if we have some valid numbers, stop if we don't
if ( !decimal.TryParse(curPurc1.Value.ToString(), out curPurc1d)
|| !decimal.TryParse(curPurc2.Value.ToString(), out curPurc2d)
|| !decimal.TryParse(curPurc3.Value.ToString(), out curPurc3d)
|| !decimal.TryParse(curPurc4.Value.ToString(), out curPurc4d)
|| !decimal.TryParse(curItem1Tot.Value.ToString(), out curItem1Totd)
|| !decimal.TryParse(curItem2Tot.Value.ToString(), out curItem2Totd)
|| !decimal.TryParse(curItem3Tot.Value.ToString(), out curItem3Totd)
|| !decimal.TryParse(curItem4Tot.Value.ToString(), out curItem4Totd)
|| !decimal.TryParse(LessItem1Cost.Value.ToString(), out LessItem1Costd)
|| !decimal.TryParse(LessItem2Cost.Value.ToString(), out LessItem2Costd)
|| !decimal.TryParse(LessItem3Cost.Value.ToString(), out LessItem3Costd)
|| !decimal.TryParse(LessItem4Cost.Value.ToString(), out LessItem4Costd)
|| !decimal.TryParse(LessItem5Cost.Value.ToString(), out LessItem5Costd)
)
return;
//For each of the 4 'items', try to get the value as a decimal, but only if the related checkbox is checked.
if (Item1RateYN.Checked)
{
decimal.TryParse(curItem1Cost.Value.ToString(), out ditem1Cost);
}
if (Item2RateYN.Checked)
{
decimal.TryParse(curItem2Cost.Value.ToString(), out ditem2Cost);
}
if (Item3RateYN.Checked)
{
decimal.TryParse(curItem3Cost.Value.ToString(), out ditem3Cost);
}
if (Item4RateYN.Checked)
{
decimal.TryParse(curItem4Cost.Value.ToString(), out ditem3Cost);
}
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1d + curPurc2d + curPurc3d + curPurc4d + curItem1Totd + curItem2Totd + curItem3Totd + curItem4Totd
+ ditem1Cost + ditem2Cost + ditem3Cost + ditem4Cost;
subtotal.Value = subtotals;
//Get total minus the cost of the property (curPurc1d)
costPlusSubTotal.Value = subtotals - curPurc1d;
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Costd + LessItem2Costd + LessItem3Costd + LessItem4Costd + LessItem5Costd;
totalLess.Value = lessTotals;
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//set the final figure into the relevant field
balanceDueTot.Value = total;
}
c# winforms
$endgroup$
add a comment |
$begingroup$
I've got 17 currency fields on a form and need to do some calculations with their values. I wrote this method some time ago and have come back to try and refactor it to make it more efficient and/or more readable. Could I have some pointers please?
Note: The variables appended with d
are the decimals used to hold the field values, their counterparts without the d
are the fields themselves. I am aware that variables could do with renaming to make more sense. Hopefully it's fairly clear what this is doing by looking at the comments.
private void getTotals() {
//declarations of decimal variables
decimal curPurc1d; decimal curPurc2d; decimal curPurc3d; decimal curPurc4d; //purItemxCost
decimal curItem1Totd; decimal curItem2Totd; decimal curItem3Totd; decimal curItem4Totd; //curItemxTot
decimal LessItem1Costd; decimal LessItem2Costd; decimal LessItem3Costd; decimal LessItem4Costd; decimal LessItem5Costd; //LessItemxCost
decimal ditem1Cost = 0; decimal ditem2Cost = 0; decimal ditem3Cost = 0; decimal ditem4Cost = 0; //Full cost of items
//Check if we have some valid numbers, stop if we don't
if ( !decimal.TryParse(curPurc1.Value.ToString(), out curPurc1d)
|| !decimal.TryParse(curPurc2.Value.ToString(), out curPurc2d)
|| !decimal.TryParse(curPurc3.Value.ToString(), out curPurc3d)
|| !decimal.TryParse(curPurc4.Value.ToString(), out curPurc4d)
|| !decimal.TryParse(curItem1Tot.Value.ToString(), out curItem1Totd)
|| !decimal.TryParse(curItem2Tot.Value.ToString(), out curItem2Totd)
|| !decimal.TryParse(curItem3Tot.Value.ToString(), out curItem3Totd)
|| !decimal.TryParse(curItem4Tot.Value.ToString(), out curItem4Totd)
|| !decimal.TryParse(LessItem1Cost.Value.ToString(), out LessItem1Costd)
|| !decimal.TryParse(LessItem2Cost.Value.ToString(), out LessItem2Costd)
|| !decimal.TryParse(LessItem3Cost.Value.ToString(), out LessItem3Costd)
|| !decimal.TryParse(LessItem4Cost.Value.ToString(), out LessItem4Costd)
|| !decimal.TryParse(LessItem5Cost.Value.ToString(), out LessItem5Costd)
)
return;
//For each of the 4 'items', try to get the value as a decimal, but only if the related checkbox is checked.
if (Item1RateYN.Checked)
{
decimal.TryParse(curItem1Cost.Value.ToString(), out ditem1Cost);
}
if (Item2RateYN.Checked)
{
decimal.TryParse(curItem2Cost.Value.ToString(), out ditem2Cost);
}
if (Item3RateYN.Checked)
{
decimal.TryParse(curItem3Cost.Value.ToString(), out ditem3Cost);
}
if (Item4RateYN.Checked)
{
decimal.TryParse(curItem4Cost.Value.ToString(), out ditem3Cost);
}
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1d + curPurc2d + curPurc3d + curPurc4d + curItem1Totd + curItem2Totd + curItem3Totd + curItem4Totd
+ ditem1Cost + ditem2Cost + ditem3Cost + ditem4Cost;
subtotal.Value = subtotals;
//Get total minus the cost of the property (curPurc1d)
costPlusSubTotal.Value = subtotals - curPurc1d;
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Costd + LessItem2Costd + LessItem3Costd + LessItem4Costd + LessItem5Costd;
totalLess.Value = lessTotals;
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//set the final figure into the relevant field
balanceDueTot.Value = total;
}
c# winforms
$endgroup$
I've got 17 currency fields on a form and need to do some calculations with their values. I wrote this method some time ago and have come back to try and refactor it to make it more efficient and/or more readable. Could I have some pointers please?
Note: The variables appended with d
are the decimals used to hold the field values, their counterparts without the d
are the fields themselves. I am aware that variables could do with renaming to make more sense. Hopefully it's fairly clear what this is doing by looking at the comments.
private void getTotals() {
//declarations of decimal variables
decimal curPurc1d; decimal curPurc2d; decimal curPurc3d; decimal curPurc4d; //purItemxCost
decimal curItem1Totd; decimal curItem2Totd; decimal curItem3Totd; decimal curItem4Totd; //curItemxTot
decimal LessItem1Costd; decimal LessItem2Costd; decimal LessItem3Costd; decimal LessItem4Costd; decimal LessItem5Costd; //LessItemxCost
decimal ditem1Cost = 0; decimal ditem2Cost = 0; decimal ditem3Cost = 0; decimal ditem4Cost = 0; //Full cost of items
//Check if we have some valid numbers, stop if we don't
if ( !decimal.TryParse(curPurc1.Value.ToString(), out curPurc1d)
|| !decimal.TryParse(curPurc2.Value.ToString(), out curPurc2d)
|| !decimal.TryParse(curPurc3.Value.ToString(), out curPurc3d)
|| !decimal.TryParse(curPurc4.Value.ToString(), out curPurc4d)
|| !decimal.TryParse(curItem1Tot.Value.ToString(), out curItem1Totd)
|| !decimal.TryParse(curItem2Tot.Value.ToString(), out curItem2Totd)
|| !decimal.TryParse(curItem3Tot.Value.ToString(), out curItem3Totd)
|| !decimal.TryParse(curItem4Tot.Value.ToString(), out curItem4Totd)
|| !decimal.TryParse(LessItem1Cost.Value.ToString(), out LessItem1Costd)
|| !decimal.TryParse(LessItem2Cost.Value.ToString(), out LessItem2Costd)
|| !decimal.TryParse(LessItem3Cost.Value.ToString(), out LessItem3Costd)
|| !decimal.TryParse(LessItem4Cost.Value.ToString(), out LessItem4Costd)
|| !decimal.TryParse(LessItem5Cost.Value.ToString(), out LessItem5Costd)
)
return;
//For each of the 4 'items', try to get the value as a decimal, but only if the related checkbox is checked.
if (Item1RateYN.Checked)
{
decimal.TryParse(curItem1Cost.Value.ToString(), out ditem1Cost);
}
if (Item2RateYN.Checked)
{
decimal.TryParse(curItem2Cost.Value.ToString(), out ditem2Cost);
}
if (Item3RateYN.Checked)
{
decimal.TryParse(curItem3Cost.Value.ToString(), out ditem3Cost);
}
if (Item4RateYN.Checked)
{
decimal.TryParse(curItem4Cost.Value.ToString(), out ditem3Cost);
}
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1d + curPurc2d + curPurc3d + curPurc4d + curItem1Totd + curItem2Totd + curItem3Totd + curItem4Totd
+ ditem1Cost + ditem2Cost + ditem3Cost + ditem4Cost;
subtotal.Value = subtotals;
//Get total minus the cost of the property (curPurc1d)
costPlusSubTotal.Value = subtotals - curPurc1d;
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Costd + LessItem2Costd + LessItem3Costd + LessItem4Costd + LessItem5Costd;
totalLess.Value = lessTotals;
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//set the final figure into the relevant field
balanceDueTot.Value = total;
}
c# winforms
c# winforms
edited Dec 12 '18 at 21:01
200_success
129k15153415
129k15153415
asked Dec 12 '18 at 16:29
Syntax ErrorSyntax Error
1605
1605
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
Two words: loops and arrays.
There a bunch of variable names that differ only by a number. Whenever you see code like this, you can probably clean it up by creating a collection of those things and looping over them. Basically, you have a checkbox and text field repeated multiple times, and one field where you display the total of all checked fields.
- Create a user control to encapsulate the checkbox and text field
- Make sure this user control has a public property
decimal TotalCost { get }
that will:
- Return the decimal-parsed values of
total - cost
for the fields when checked, and zero when unchecked. - Throws an exception if the decimal cannot be parsed
- Return the decimal-parsed values of
- Expose a boolean property
bool IsValid
that returns true when the user as entered a valid decimals - Expose a public property
bool IsChecked
that returns whether or not the checkbox is checked - Create a collection of these user controls, one for each checkbox/text field combo on screen
Now loop and process:
decimal purchaseTotal = 0m;
decimal totalAmount = 0m;
foreach (var control in PurchasedItems.Where(p => p.IsChecked && p.IsValid))
{
purchaseTotal += control.TotalCost;
}
I left some details out, but your code isn't really clear on what the UI looks like, or what the business use case is, but it really just boils down to:
- Create an abstraction for what each combo of controls represents (e.g. create a new user control)
- Create a collection of these controls
- Loop and calculate
$endgroup$
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
@radarbob: That's what theIsValid
property is for. Check it before calling the TotalCost getter.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
Besides, if you don't checkIsValid
first, and callTotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
add a comment |
$begingroup$
Assuming the fields have definitions like the following
public class TextboxControl
{
public object Value { get; set; }
}
public class CheckboxControl
{
public bool Checked { get; set; }
}
I would create some extensions for them in order to reduce code duplication and increase readability, and pretty much this is the core idea.
public static class ControlsExtensions
{
public static bool HasValue(this TextboxControl source)
=> decimal.TryParse(source.Value.ToString(), out _);
public static decimal GetValue(this TextboxControl source)
=> decimal.Parse(source.Value.ToString());
public static decimal GetOptionalValue(this TextboxControl source, CheckboxControl checkbox)
{
if (checkbox.Checked)
{
decimal value = decimal.TryParse(source.Value.ToString(), out value) ? value : 0;
return value;
}
return default(decimal);
}
}
Then my next step in refactoring would to use these extensions and rearrange a bit the lines to increase cohesion
private void getTotals()
{
//Check if we have some valid numbers, stop if we don't
if ( !curPurc1.HasValue()
|| !curPurc2.HasValue()
|| !curPurc3.HasValue()
|| !curPurc4.HasValue()
|| !curItem1Tot.HasValue()
|| !curItem2Tot.HasValue()
|| !curItem3Tot.HasValue()
|| !curItem4Tot.HasValue()
|| !LessItem1Cost.HasValue()
|| !LessItem2Cost.HasValue()
|| !LessItem3Cost.HasValue()
|| !LessItem4Cost.HasValue()
|| !LessItem5Cost.HasValue()
)
return;
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1.GetValue() + curPurc2.GetValue() + curPurc3.GetValue()
+ curPurc4.GetValue() + curItem1Tot.GetValue() + curItem2Tot.GetValue()
+ curItem3Tot.GetValue() + curItem4Tot.GetValue()
+ Item1RateYN.GetOptionalValue() + Item2RateYN.GetOptionalValue() + Item3RateYN.GetOptionalValue() + Item4RateYN.GetOptionalValue();
//Get total minus the cost of the property (curPurc1d)
decimal plusSubTotal = subtotals - curPurc1.GetValue();
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Cost.GetValue() + LessItem2Cost.GetValue() + LessItem3Cost.GetValue() + LessItem4Cost.GetValue() + LessItem5Cost.GetValue();
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//update the relevant UI field
costPlusSubTotal.Value = plusSubTotal;
subtotal.Value = subtotals;
balanceDueTot.Value = total;
totalLess.Value = lessTotals;
}
Then to make the code more C#-like, I would
use
var
instead ofdecimal
rename
getTotals
toGetTotals
- use
_camelCase
for private fileds (soLess
becomes_less
) - reduce redundant parenthesis from
(subtotals - lessTotals)
- use brackets
{}
for thereturn
statement
Notice that I also grouped the update of the UI at the end of the method.
I would have some comments also for the name of the method itself as GetTotals
implies that the method returns something, but the return signature is void
. One idea is to use something like CalculateTotals
.
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209540%2fcalculate-subtotals-and-totals-from-a-form-with-many-decimal-values%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Two words: loops and arrays.
There a bunch of variable names that differ only by a number. Whenever you see code like this, you can probably clean it up by creating a collection of those things and looping over them. Basically, you have a checkbox and text field repeated multiple times, and one field where you display the total of all checked fields.
- Create a user control to encapsulate the checkbox and text field
- Make sure this user control has a public property
decimal TotalCost { get }
that will:
- Return the decimal-parsed values of
total - cost
for the fields when checked, and zero when unchecked. - Throws an exception if the decimal cannot be parsed
- Return the decimal-parsed values of
- Expose a boolean property
bool IsValid
that returns true when the user as entered a valid decimals - Expose a public property
bool IsChecked
that returns whether or not the checkbox is checked - Create a collection of these user controls, one for each checkbox/text field combo on screen
Now loop and process:
decimal purchaseTotal = 0m;
decimal totalAmount = 0m;
foreach (var control in PurchasedItems.Where(p => p.IsChecked && p.IsValid))
{
purchaseTotal += control.TotalCost;
}
I left some details out, but your code isn't really clear on what the UI looks like, or what the business use case is, but it really just boils down to:
- Create an abstraction for what each combo of controls represents (e.g. create a new user control)
- Create a collection of these controls
- Loop and calculate
$endgroup$
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
@radarbob: That's what theIsValid
property is for. Check it before calling the TotalCost getter.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
Besides, if you don't checkIsValid
first, and callTotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
add a comment |
$begingroup$
Two words: loops and arrays.
There a bunch of variable names that differ only by a number. Whenever you see code like this, you can probably clean it up by creating a collection of those things and looping over them. Basically, you have a checkbox and text field repeated multiple times, and one field where you display the total of all checked fields.
- Create a user control to encapsulate the checkbox and text field
- Make sure this user control has a public property
decimal TotalCost { get }
that will:
- Return the decimal-parsed values of
total - cost
for the fields when checked, and zero when unchecked. - Throws an exception if the decimal cannot be parsed
- Return the decimal-parsed values of
- Expose a boolean property
bool IsValid
that returns true when the user as entered a valid decimals - Expose a public property
bool IsChecked
that returns whether or not the checkbox is checked - Create a collection of these user controls, one for each checkbox/text field combo on screen
Now loop and process:
decimal purchaseTotal = 0m;
decimal totalAmount = 0m;
foreach (var control in PurchasedItems.Where(p => p.IsChecked && p.IsValid))
{
purchaseTotal += control.TotalCost;
}
I left some details out, but your code isn't really clear on what the UI looks like, or what the business use case is, but it really just boils down to:
- Create an abstraction for what each combo of controls represents (e.g. create a new user control)
- Create a collection of these controls
- Loop and calculate
$endgroup$
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
@radarbob: That's what theIsValid
property is for. Check it before calling the TotalCost getter.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
Besides, if you don't checkIsValid
first, and callTotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
add a comment |
$begingroup$
Two words: loops and arrays.
There a bunch of variable names that differ only by a number. Whenever you see code like this, you can probably clean it up by creating a collection of those things and looping over them. Basically, you have a checkbox and text field repeated multiple times, and one field where you display the total of all checked fields.
- Create a user control to encapsulate the checkbox and text field
- Make sure this user control has a public property
decimal TotalCost { get }
that will:
- Return the decimal-parsed values of
total - cost
for the fields when checked, and zero when unchecked. - Throws an exception if the decimal cannot be parsed
- Return the decimal-parsed values of
- Expose a boolean property
bool IsValid
that returns true when the user as entered a valid decimals - Expose a public property
bool IsChecked
that returns whether or not the checkbox is checked - Create a collection of these user controls, one for each checkbox/text field combo on screen
Now loop and process:
decimal purchaseTotal = 0m;
decimal totalAmount = 0m;
foreach (var control in PurchasedItems.Where(p => p.IsChecked && p.IsValid))
{
purchaseTotal += control.TotalCost;
}
I left some details out, but your code isn't really clear on what the UI looks like, or what the business use case is, but it really just boils down to:
- Create an abstraction for what each combo of controls represents (e.g. create a new user control)
- Create a collection of these controls
- Loop and calculate
$endgroup$
Two words: loops and arrays.
There a bunch of variable names that differ only by a number. Whenever you see code like this, you can probably clean it up by creating a collection of those things and looping over them. Basically, you have a checkbox and text field repeated multiple times, and one field where you display the total of all checked fields.
- Create a user control to encapsulate the checkbox and text field
- Make sure this user control has a public property
decimal TotalCost { get }
that will:
- Return the decimal-parsed values of
total - cost
for the fields when checked, and zero when unchecked. - Throws an exception if the decimal cannot be parsed
- Return the decimal-parsed values of
- Expose a boolean property
bool IsValid
that returns true when the user as entered a valid decimals - Expose a public property
bool IsChecked
that returns whether or not the checkbox is checked - Create a collection of these user controls, one for each checkbox/text field combo on screen
Now loop and process:
decimal purchaseTotal = 0m;
decimal totalAmount = 0m;
foreach (var control in PurchasedItems.Where(p => p.IsChecked && p.IsValid))
{
purchaseTotal += control.TotalCost;
}
I left some details out, but your code isn't really clear on what the UI looks like, or what the business use case is, but it really just boils down to:
- Create an abstraction for what each combo of controls represents (e.g. create a new user control)
- Create a collection of these controls
- Loop and calculate
answered Dec 12 '18 at 18:22
Greg BurghardtGreg Burghardt
4,971620
4,971620
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
@radarbob: That's what theIsValid
property is for. Check it before calling the TotalCost getter.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
Besides, if you don't checkIsValid
first, and callTotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
add a comment |
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
@radarbob: That's what theIsValid
property is for. Check it before calling the TotalCost getter.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
Besides, if you don't checkIsValid
first, and callTotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.
$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
Do not throw an exception if the entry cannot be parsed. Exceptions should be reserved for "continued processing is not possible" conditions. And exceptions are relatively process intensive operations. A failed parse is easily captured w/o exceptions and it's obvious what to do with it, tell the user to re-enter. This is normal run of the mill user entry error handling, do not use exceptions in these cases.
$endgroup$
– radarbob
Dec 13 '18 at 6:24
$begingroup$
@radarbob: That's what the
IsValid
property is for. Check it before calling the TotalCost getter.$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
@radarbob: That's what the
IsValid
property is for. Check it before calling the TotalCost getter.$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:08
$begingroup$
Besides, if you don't check
IsValid
first, and call TotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
$begingroup$
Besides, if you don't check
IsValid
first, and call TotalCost
and it cannot parse the input, then "continued processing is not possible" and an exception is appropriate.$endgroup$
– Greg Burghardt
Dec 13 '18 at 13:09
add a comment |
$begingroup$
Assuming the fields have definitions like the following
public class TextboxControl
{
public object Value { get; set; }
}
public class CheckboxControl
{
public bool Checked { get; set; }
}
I would create some extensions for them in order to reduce code duplication and increase readability, and pretty much this is the core idea.
public static class ControlsExtensions
{
public static bool HasValue(this TextboxControl source)
=> decimal.TryParse(source.Value.ToString(), out _);
public static decimal GetValue(this TextboxControl source)
=> decimal.Parse(source.Value.ToString());
public static decimal GetOptionalValue(this TextboxControl source, CheckboxControl checkbox)
{
if (checkbox.Checked)
{
decimal value = decimal.TryParse(source.Value.ToString(), out value) ? value : 0;
return value;
}
return default(decimal);
}
}
Then my next step in refactoring would to use these extensions and rearrange a bit the lines to increase cohesion
private void getTotals()
{
//Check if we have some valid numbers, stop if we don't
if ( !curPurc1.HasValue()
|| !curPurc2.HasValue()
|| !curPurc3.HasValue()
|| !curPurc4.HasValue()
|| !curItem1Tot.HasValue()
|| !curItem2Tot.HasValue()
|| !curItem3Tot.HasValue()
|| !curItem4Tot.HasValue()
|| !LessItem1Cost.HasValue()
|| !LessItem2Cost.HasValue()
|| !LessItem3Cost.HasValue()
|| !LessItem4Cost.HasValue()
|| !LessItem5Cost.HasValue()
)
return;
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1.GetValue() + curPurc2.GetValue() + curPurc3.GetValue()
+ curPurc4.GetValue() + curItem1Tot.GetValue() + curItem2Tot.GetValue()
+ curItem3Tot.GetValue() + curItem4Tot.GetValue()
+ Item1RateYN.GetOptionalValue() + Item2RateYN.GetOptionalValue() + Item3RateYN.GetOptionalValue() + Item4RateYN.GetOptionalValue();
//Get total minus the cost of the property (curPurc1d)
decimal plusSubTotal = subtotals - curPurc1.GetValue();
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Cost.GetValue() + LessItem2Cost.GetValue() + LessItem3Cost.GetValue() + LessItem4Cost.GetValue() + LessItem5Cost.GetValue();
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//update the relevant UI field
costPlusSubTotal.Value = plusSubTotal;
subtotal.Value = subtotals;
balanceDueTot.Value = total;
totalLess.Value = lessTotals;
}
Then to make the code more C#-like, I would
use
var
instead ofdecimal
rename
getTotals
toGetTotals
- use
_camelCase
for private fileds (soLess
becomes_less
) - reduce redundant parenthesis from
(subtotals - lessTotals)
- use brackets
{}
for thereturn
statement
Notice that I also grouped the update of the UI at the end of the method.
I would have some comments also for the name of the method itself as GetTotals
implies that the method returns something, but the return signature is void
. One idea is to use something like CalculateTotals
.
$endgroup$
add a comment |
$begingroup$
Assuming the fields have definitions like the following
public class TextboxControl
{
public object Value { get; set; }
}
public class CheckboxControl
{
public bool Checked { get; set; }
}
I would create some extensions for them in order to reduce code duplication and increase readability, and pretty much this is the core idea.
public static class ControlsExtensions
{
public static bool HasValue(this TextboxControl source)
=> decimal.TryParse(source.Value.ToString(), out _);
public static decimal GetValue(this TextboxControl source)
=> decimal.Parse(source.Value.ToString());
public static decimal GetOptionalValue(this TextboxControl source, CheckboxControl checkbox)
{
if (checkbox.Checked)
{
decimal value = decimal.TryParse(source.Value.ToString(), out value) ? value : 0;
return value;
}
return default(decimal);
}
}
Then my next step in refactoring would to use these extensions and rearrange a bit the lines to increase cohesion
private void getTotals()
{
//Check if we have some valid numbers, stop if we don't
if ( !curPurc1.HasValue()
|| !curPurc2.HasValue()
|| !curPurc3.HasValue()
|| !curPurc4.HasValue()
|| !curItem1Tot.HasValue()
|| !curItem2Tot.HasValue()
|| !curItem3Tot.HasValue()
|| !curItem4Tot.HasValue()
|| !LessItem1Cost.HasValue()
|| !LessItem2Cost.HasValue()
|| !LessItem3Cost.HasValue()
|| !LessItem4Cost.HasValue()
|| !LessItem5Cost.HasValue()
)
return;
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1.GetValue() + curPurc2.GetValue() + curPurc3.GetValue()
+ curPurc4.GetValue() + curItem1Tot.GetValue() + curItem2Tot.GetValue()
+ curItem3Tot.GetValue() + curItem4Tot.GetValue()
+ Item1RateYN.GetOptionalValue() + Item2RateYN.GetOptionalValue() + Item3RateYN.GetOptionalValue() + Item4RateYN.GetOptionalValue();
//Get total minus the cost of the property (curPurc1d)
decimal plusSubTotal = subtotals - curPurc1.GetValue();
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Cost.GetValue() + LessItem2Cost.GetValue() + LessItem3Cost.GetValue() + LessItem4Cost.GetValue() + LessItem5Cost.GetValue();
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//update the relevant UI field
costPlusSubTotal.Value = plusSubTotal;
subtotal.Value = subtotals;
balanceDueTot.Value = total;
totalLess.Value = lessTotals;
}
Then to make the code more C#-like, I would
use
var
instead ofdecimal
rename
getTotals
toGetTotals
- use
_camelCase
for private fileds (soLess
becomes_less
) - reduce redundant parenthesis from
(subtotals - lessTotals)
- use brackets
{}
for thereturn
statement
Notice that I also grouped the update of the UI at the end of the method.
I would have some comments also for the name of the method itself as GetTotals
implies that the method returns something, but the return signature is void
. One idea is to use something like CalculateTotals
.
$endgroup$
add a comment |
$begingroup$
Assuming the fields have definitions like the following
public class TextboxControl
{
public object Value { get; set; }
}
public class CheckboxControl
{
public bool Checked { get; set; }
}
I would create some extensions for them in order to reduce code duplication and increase readability, and pretty much this is the core idea.
public static class ControlsExtensions
{
public static bool HasValue(this TextboxControl source)
=> decimal.TryParse(source.Value.ToString(), out _);
public static decimal GetValue(this TextboxControl source)
=> decimal.Parse(source.Value.ToString());
public static decimal GetOptionalValue(this TextboxControl source, CheckboxControl checkbox)
{
if (checkbox.Checked)
{
decimal value = decimal.TryParse(source.Value.ToString(), out value) ? value : 0;
return value;
}
return default(decimal);
}
}
Then my next step in refactoring would to use these extensions and rearrange a bit the lines to increase cohesion
private void getTotals()
{
//Check if we have some valid numbers, stop if we don't
if ( !curPurc1.HasValue()
|| !curPurc2.HasValue()
|| !curPurc3.HasValue()
|| !curPurc4.HasValue()
|| !curItem1Tot.HasValue()
|| !curItem2Tot.HasValue()
|| !curItem3Tot.HasValue()
|| !curItem4Tot.HasValue()
|| !LessItem1Cost.HasValue()
|| !LessItem2Cost.HasValue()
|| !LessItem3Cost.HasValue()
|| !LessItem4Cost.HasValue()
|| !LessItem5Cost.HasValue()
)
return;
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1.GetValue() + curPurc2.GetValue() + curPurc3.GetValue()
+ curPurc4.GetValue() + curItem1Tot.GetValue() + curItem2Tot.GetValue()
+ curItem3Tot.GetValue() + curItem4Tot.GetValue()
+ Item1RateYN.GetOptionalValue() + Item2RateYN.GetOptionalValue() + Item3RateYN.GetOptionalValue() + Item4RateYN.GetOptionalValue();
//Get total minus the cost of the property (curPurc1d)
decimal plusSubTotal = subtotals - curPurc1.GetValue();
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Cost.GetValue() + LessItem2Cost.GetValue() + LessItem3Cost.GetValue() + LessItem4Cost.GetValue() + LessItem5Cost.GetValue();
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//update the relevant UI field
costPlusSubTotal.Value = plusSubTotal;
subtotal.Value = subtotals;
balanceDueTot.Value = total;
totalLess.Value = lessTotals;
}
Then to make the code more C#-like, I would
use
var
instead ofdecimal
rename
getTotals
toGetTotals
- use
_camelCase
for private fileds (soLess
becomes_less
) - reduce redundant parenthesis from
(subtotals - lessTotals)
- use brackets
{}
for thereturn
statement
Notice that I also grouped the update of the UI at the end of the method.
I would have some comments also for the name of the method itself as GetTotals
implies that the method returns something, but the return signature is void
. One idea is to use something like CalculateTotals
.
$endgroup$
Assuming the fields have definitions like the following
public class TextboxControl
{
public object Value { get; set; }
}
public class CheckboxControl
{
public bool Checked { get; set; }
}
I would create some extensions for them in order to reduce code duplication and increase readability, and pretty much this is the core idea.
public static class ControlsExtensions
{
public static bool HasValue(this TextboxControl source)
=> decimal.TryParse(source.Value.ToString(), out _);
public static decimal GetValue(this TextboxControl source)
=> decimal.Parse(source.Value.ToString());
public static decimal GetOptionalValue(this TextboxControl source, CheckboxControl checkbox)
{
if (checkbox.Checked)
{
decimal value = decimal.TryParse(source.Value.ToString(), out value) ? value : 0;
return value;
}
return default(decimal);
}
}
Then my next step in refactoring would to use these extensions and rearrange a bit the lines to increase cohesion
private void getTotals()
{
//Check if we have some valid numbers, stop if we don't
if ( !curPurc1.HasValue()
|| !curPurc2.HasValue()
|| !curPurc3.HasValue()
|| !curPurc4.HasValue()
|| !curItem1Tot.HasValue()
|| !curItem2Tot.HasValue()
|| !curItem3Tot.HasValue()
|| !curItem4Tot.HasValue()
|| !LessItem1Cost.HasValue()
|| !LessItem2Cost.HasValue()
|| !LessItem3Cost.HasValue()
|| !LessItem4Cost.HasValue()
|| !LessItem5Cost.HasValue()
)
return;
//Add up some values which are always part of the subtotal and then the ditemx ones, which will be 0 or set to a numerical value depending if the checkbox is checked
decimal subtotals = curPurc1.GetValue() + curPurc2.GetValue() + curPurc3.GetValue()
+ curPurc4.GetValue() + curItem1Tot.GetValue() + curItem2Tot.GetValue()
+ curItem3Tot.GetValue() + curItem4Tot.GetValue()
+ Item1RateYN.GetOptionalValue() + Item2RateYN.GetOptionalValue() + Item3RateYN.GetOptionalValue() + Item4RateYN.GetOptionalValue();
//Get total minus the cost of the property (curPurc1d)
decimal plusSubTotal = subtotals - curPurc1.GetValue();
//add up all the "less" items to know how much to reduce by
decimal lessTotals = LessItem1Cost.GetValue() + LessItem2Cost.GetValue() + LessItem3Cost.GetValue() + LessItem4Cost.GetValue() + LessItem5Cost.GetValue();
//Total Balance due
//subtotal minus the 'less' values total
decimal total = (subtotals - lessTotals);
//update the relevant UI field
costPlusSubTotal.Value = plusSubTotal;
subtotal.Value = subtotals;
balanceDueTot.Value = total;
totalLess.Value = lessTotals;
}
Then to make the code more C#-like, I would
use
var
instead ofdecimal
rename
getTotals
toGetTotals
- use
_camelCase
for private fileds (soLess
becomes_less
) - reduce redundant parenthesis from
(subtotals - lessTotals)
- use brackets
{}
for thereturn
statement
Notice that I also grouped the update of the UI at the end of the method.
I would have some comments also for the name of the method itself as GetTotals
implies that the method returns something, but the return signature is void
. One idea is to use something like CalculateTotals
.
edited Dec 12 '18 at 17:44
answered Dec 12 '18 at 17:37
Adrian IftodeAdrian Iftode
297111
297111
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209540%2fcalculate-subtotals-and-totals-from-a-form-with-many-decimal-values%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown