Refactoring messy if else statement - if-statement

I am working on a legacy code base which has the following snippet:
if ((results[0].Length == 0))
customerName = "";
else
customerName = results[0].Substring(18);
if ((results[1].Length == 0))
meterSerialNumber = "";
else
meterSerialNumber = results[1];
if ((results[2].Length == 0))
customerID = "";
else
customerID = results[2];
if ((results[3].Length == 0))
meterCreditAmount = "";
else
meterCreditAmount = results[3];
if ((results[4].Length == 0))
debtInstallmentDeduction = "";
else
debtInstallmentDeduction = results[4];
if ((results[5].Length == 0))
vatOnEnergyAmount = "";
else
vatOnEnergyAmount = results[5];
if ((results[6].Length == 0))
vatOnDebt = "";
else
vatOnDebt = results[6];
if ((results[7].Length == 0))
outstandingDebtAmount = "";
else
outstandingDebtAmount = results[7];
if ((results[8].Length == 0))
tariffCategory = "";
else
tariffCategory = results[8];
if ((results[9].Length == 0))
tariffId = "";
else
tariffId = results[9];
if ((results[10].Length == 0))
encryptedToken1 = "";
else
encryptedToken1 = results[10];
if ((results[11].Length == 0))
encryptedToken2 = "";
else
encryptedToken2 = results[11];
if ((results[12].Length == 0))
encryptedToken3 = "";
else
encryptedToken3 = results[12];
if ((results[13].Length == 0))
encryptedToken4 = "";
else
encryptedToken4 = results[13];
if ((results[14].Length == 0))
systemMessage = "";
else
systemMessage = results[14];
if ((results[15].Length == 0))
customerMessage = "";
else
customerMessage = results[15];
if ((results[16].Length == 0))
predefinedMessage = "";
else
predefinedMessage = results[16];
if ((results[17].Length == 0))
transactionAcknowledgeNumber = "";
else
transactionAcknowledgeNumber = results[17];
What would be the best way to refactor this for acceptable coding standards? Would it be acceptable to make this a case statement instead?

This is not a case-wise execution so it can't be refactored to a switch-case. However it can be converted to functional code and then it factored out into a separate method so that the "ugly" part is hidden behind a method call.
Step#1 - Making the code functional
Here, we rewrite the code by following functional code writing practices. The rewritten code will look like:
customerName = (results[0].Length == 0) ? "" : results[0].Substring(18);
meterSerialNumber = (results[1].Length == 0) ? "" : results[1];
customerID = (results[2].Length == 0) ? "" : results[2];
meterCreditAmount = (results[3].Length == 0) ? "" : results[3];
debtInstallmentDeduction = (results[4].Length == 0) ? "" : results[4];
vatOnEnergyAmount = (results[5].Length == 0) ? "" : results[5];
.
.
.
transactionAcknowledgeNumber = (results[17].Length == 0) ? "" : results[17];
There are numerous advantages of writing the code this way. Important ones include:
terseness of code;
values being initialized at one place (by means of ternary operator) instead of two (one in if and another in else clause).
Step#2 - Factoring out the method
Now that the values are being initialized functionally, you can create a class (or you may be already having this class) containing the properties customerName, meterSerialNumber, ..., transactionAcknowledgeNumber. Either the constructor of the class can be designed to read the results and populate the class members or you may write a method to read the results. So it will look like:
ResultValues resultVal = new ResultValues();
resultVal.Read(results);
.
.
.
//Accessing the values later in the code
Print(resultVal.customerName);
...
PS:
1. I admit that ResultValues may not be a good class to make. Alternatively, you may create multiple classes by clubbing the related data and then have the Read() method of those classes read the values from results.
2. The essential idea of Step#2 is to factor out the "ugly" part to another simple and readable method call(s).

Related

split a string by commas, but commas in a token

Let's say I have this:
something,"another thing"
This can be split easily with a normal split function.
Now I want to have more complicated syntax and I do:
something,"in a string, oooh",rgba(4,2,0)
This does not work with a regular split function.
I tried using things like replacing commas inside of specific types of tokens, but that became too over-complicated and I feel there has to be a better way.
Then I tried with regular expressions, which worked, until I had to add a new feature, which wouldn't work with the regexp I had (which was pretty bad), also regexp matches can be slow, and this is supposed to be as fast as possible.
What would be a better way to solve this?
Here is the source repo for extra context https://github.com/hyprland-community/hyprparse
And the format in question is the hyprland config format
Iterate over the string keeping a context state:
None
Inside a "..."
Inside a (...)
Inside a context, comma has no separator meaning.
Limitations: This is a midnight hack!
See also Rust Playground
fn split(s: String) -> Vec<String> {
let mut context = None;
let mut i = 0;
let mut start = 0;
let mut items = Vec::new();
for c in s.chars() {
if context == Some('"') {
if c == '"' {
context = None;
}
i = i+1;
continue;
} else if context == Some('(') {
if c == ')' {
context = None;
}
i = i+1;
continue;
}
if c == '"' || c == '(' {
context = Some(c);
}
if c == ',' && context.is_none() {
items.push(s[start..i].to_string());
start = i + 1;
}
i = i+1;
}
items.push(s[start..i].to_string());
items
}
fn main() {
let s = "something,\"in a string, oooh\",rgba(4,2,0)".to_string();
println!("{:?}", split(s));
// -> ["something", "\"in a string, oooh\"", "rgba(4,2,0)"]
}
Thanks for all the help everyone, I eventually came up with my own solution with the help of people I knew IRL, here is my solution:
fn previous_rgb(index: usize, chars: Vec<char>) -> bool {
let string: String = chars[index-3..index].iter().collect();
let string = string.as_str();
if string == "gba" || string == "rgb" {
true
} else {
false
}
}
fn splitter<Str: ToString>(s: Str) -> Vec<String> {
let mut is_in_rgb = false;
let mut is_in_str = false;
let mut last = 0;
let chars: Vec<_> = s.to_string().chars().collect();
let mut final_str = vec![];
for (index, c) in chars.iter().enumerate() {
if *c == '(' && previous_rgb(index, chars.clone()) && !is_in_str {
is_in_rgb = true;
} else if *c == ')' && is_in_rgb && !is_in_str {
is_in_rgb = false;
} else if *c == '"' && is_in_str {
is_in_str = false;
} else if *c == '"' && !is_in_str {
is_in_str = true;
} else if *c == ',' && !is_in_str && !is_in_rgb {
final_str.push(chars[last..index].iter().collect());
last = index + 1
};
}
final_str.push(chars[last..].iter().collect());
final_str
}
fn main() {
let splitted = splitter(r#"test,test: rgba(5,4,3),"test2, seperated""#);
println!("{splitted:?}");
}

Changing if-else into switch statements?

I am using multiple else/if statements and I want to use switch statements. I tried but don't know how to fill in multiple answers.
How can I convert the following else/if statements to switch?
if (this.state.item.type === 'dashboard') {
settings.view_name = this.state.view_name;
settings.layout = this.state.layout;
settings.inline_edit = this.state.inlineEdit;
settings.show_add = this.state.showAdd;
settings.tab_queries = this.state.tabQueries;
settings.carousel = this.state.carousel;
settings.template = this.state.template;
settings.sort = this.state.sort;
settings.templateOptions = this.state.templateOptions;
} else if (this.state.item.type === 'list_option_percentage_conditions') {
settings.pie_charts = this.state.pie_charts;
} else if (this.state.item.type === 'count_list_options') {
settings.count_list_field = this.state.count_list_field;
settings.date_field = this.state.date_field;
settings.use_creation_date = this.state.use_creation_date;
settings.scheme = this.state.scheme;
} else if (this.state.item.type === 'sum_fields') {
settings.sum_field = this.state.sum_field;
settings.scheme = this.state.scheme;
}
I want it to look something like this:
switch (this.state.iten.type) {
case "dashboard":
answer = "";
break;
case "list_option_percentage_conditions":
answer = "";
case "count_list_options":
answer = "";
break;
case "sum_fields":
answer = "";
break;
}

Sitecore.Analytics.Tracker.Current is null when invoked through a pipeline

I need to redirect based on the country location the user is trying to access. For example when user trying to access http://www.example.com/ from china my site should as http://www.example.com/zh. I am checking using the sitecore tracker in pipeline process to get the country code using the below method.
public void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull(args, "args");
if (HttpContext.Current == null
|| Context.Site == null
////TODO: || Sitecore.Context.PageMode...
|| Context.Database == null || Context.Site.Name == "shell" || !this._sites.Contains(Context.Site.Name))
{
return;
}
// contains path including language and query string
// (not anchor name), but not hostname.
// We can use this to add the language back into the path.
string rawPath = Sitecore.Context.RawUrl;
if (!rawPath.StartsWith("/sitecore") && !rawPath.StartsWith("/" + Sitecore.Context.Language.Name + "/") && !rawPath.StartsWith("/" + Sitecore.Context.Language.Name) && !rawPath.StartsWith("/default.aspx"))
{
string langCode = "";
if(!string.IsNullOrEmpty(GeoIPUtils.GetUserGeoIP()))
{
try
{
string country = GeoIPUtils.GetUserGeoIP();;
if (country.Trim().ToUpper() == "China".ToUpper())
langCode = "zh";
else if (country.Trim().ToUpper() == "Japan".ToUpper())
langCode = "jp";
else if (country.Trim().ToUpper() == "Thailand".ToUpper())
langCode = "th";
else
langCode = "en";
}
catch(Exception)
{
langCode = "en";
}
}
else
{
langCode = HttpContext.Current.Request.Cookies["avc#lang"].Value.ToString();
}
if (!string.IsNullOrEmpty(langCode))
{
Language language = null;
if (Language.TryParse(langCode, out language))
{
//then try to get the language item id from the language or two letter iso code
ID langID = LanguageManager.GetLanguageItemId(language, Sitecore.Context.Database);
if (!ID.IsNullOrEmpty(langID))
{
//sometimes the language found is slightly different than official language item used in SC
language = LanguageManager.GetLanguage(language.CultureInfo.TwoLetterISOLanguageName);
if (Context.Item != null)
{
List<string> availableLangs = LanguagesWithContent(Context.Item);
if (availableLangs != null && availableLangs.Count > 0 && !availableLangs.Contains(language.CultureInfo.TwoLetterISOLanguageName.ToString()))
{
langCode = availableLangs.FirstOrDefault().ToString();
}
}
else
{
langCode = "en";
}
}
else
{
langCode = "en";
}
}
}
HttpContext.Current.Response.RedirectPermanent("/" + (String.IsNullOrEmpty(langCode) ? Sitecore.Context.Language.Name : langCode) + rawPath);
}
}
Below is the GetUserGeoIP function
public static string GetUserGeoIP()
{
string countryCode = "";
try
{
countryCode = Sitecore.Analytics.Tracker.Current.Interaction.GeoData.Country;
}
catch(Exception ex)
{
Log.Error("GetUserGeoIP Error: " + ex.Message + " Source: " + ex.Source + " Stack Trace :" + ex.StackTrace + " Inner Ex : " + ex.InnerException, ex);
countryCode = "GB";
}
if (!string.IsNullOrEmpty(countryCode))
{
var countryItem = ISO3166.FromAlpha2(countryCode);
if (countryItem != null)
return countryItem.Name;
}
return "Other";
}
But I am getting an below exception
7904 10:43:25 ERROR Cannot create tracker.
Exception: System.InvalidOperationException
Message: session is not initialized
Source: Sitecore.Analytics
at Sitecore.Analytics.Data.HttpSessionContextManager.GetSession()
at Sitecore.Analytics.Pipelines.EnsureSessionContext.EnsureContext.Process(InitializeTrackerArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Analytics.DefaultTracker.EnsureSessionContext()
at Sitecore.Analytics.Pipelines.CreateTracker.GetTracker.Process(CreateTrackerArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Analytics.Tracker.Initialize()
Note: The same GetUserGeoIP method is used in API which gets the correct countryName. I am using Sitecore.NET 8.0 (rev. 151127) version
Any help on this highly appreciated
Your processor is probably too soon in the pipeline(s). You can find an overview of the request pipelines here: http://sitecoreskills.blogspot.be/2015/02/a-sitecore-8-request-from-beginning-to.html
You should put your processor after the tracker has been initialized and the geo data has been fetched. I did something similar a while ago and placed my processor in the startAnalytics pipeline.
Fetching the geo data is async so that might (will) give issues when doing this in the first request pipeline. Read this article to tackle that issue by calling UpdateGeoIpData with a timespan.

Java8: Implement multiple conditional statements with stream API

I have a code block that redirects a Cassandra query to different Cassandra tables based on the available parameters such that I check multiple logical conditions inside multiple if conditions. I'm trying my hand at java 8 and looking to reduce these conditions to lambda expressions. Here's how the code currently looks,
String processTable(String cid, String postcode, String[] ratingvalue, String ratingType) {
String table = "";
if (postcode != null && ratingvalue == null) {
table = cassconf.getTable1();
}
if (postcode != null && ratingvalue != null) {
table = cassconf.getTable2();
}
if (cid != null && ratingvalue == null) {
table = cassconf.getTable3();
}
if (cid != null && ratingvalue != null) {
table = cassconf.getTable4();
}
if (cid != null && postcode != null && ratingvalue == null) {
table = cassconf.getTable5();
}
if (cid != null && postcode != null & ratingvalue != null) {
table = cassconf.getTable6();
}
return table;
}
My problem is even if I store the arguments in a map and filter the unavailable values from the stream, I don't know how to return the final value of the table based on these 6 different conditions.
Considering that ratingvalue can only be null or non-null, you can simplify the code by writing effectively unconditional statements as such:
String processTable(String cid, String postcode, String[] ratingvalue, String ratingType) {
if (cid != null)
if(postcode != null)
return ratingvalue == null? cassconf.getTable5(): cassconf.getTable6();
else
return ratingvalue == null? cassconf.getTable3(): cassconf.getTable4();
if(postcode != null)
return ratingvalue == null? cassconf.getTable1(): cassconf.getTable2();
return "";
}
Testing the conditions with precedence first is also more efficient than testing all conditions in reversed order and overwriting results of previous evaluations.
You could also write the entire evaluation as a single condition:
String processTable(String cid, String postcode, String[] ratingvalue, String ratingType) {
return cid != null?
postcode != null? ratingvalue == null? cassconf.getTable5(): cassconf.getTable6():
ratingvalue == null? cassconf.getTable3(): cassconf.getTable4():
postcode != null? ratingvalue == null? cassconf.getTable1(): cassconf.getTable2():
"";
}
An alternative is to use a map lookup:
String processTable(String cid, String postcode, String[] ratingvalue, String ratingType) {
final int hasCID = 1, hasPostcode = 2, hasRatingValue = 4;
Map<Integer, Supplier<String>> map = new HashMap<>();
map.put(hasCID|hasPostcode, cassconf::getTable5);
map.put(hasCID|hasPostcode|hasRatingValue, cassconf::getTable6);
map.put(hasCID, cassconf::getTable3);
map.put(hasCID|hasRatingValue, cassconf::getTable4);
map.put(hasPostcode, cassconf::getTable2);
map.put(hasPostcode|hasRatingValue, cassconf::getTable1);
return map.getOrDefault(
(cid!=null? hasCID: 0) | (postcode!=null? hasPostcode: 0)
| (ratingvalue!=null? hasRatingValue: 0),
() -> "").get();
}
The key point of this alternative is that, depending on what cassconf is or when it will be initialized, the map may be prepared at an earlier stage and processTable could be simplified to the return map.getOrDefault… operation.
I was thinking this as an exercise more, nothing to do with streams though, since it can't really help you here.
You could compute a HashMap that will have O(1) time for finding a value, like so:
Map<Integer, String> map = new HashMap<>(16);
map.put(0b1110, "table-6");
map.put(0b1100, "table-5");
map.put(0b1010, "table-4");
map.put(0b1000, "table-3");
map.put(0b0110, "table-2");
map.put(0b0100, "table-1");
This corresponds to whether your cid (the 4-th most significant bit), postcode (3-rd most significant bit) and ratingValue (second most significant bit) are null or not. So these are the total of 6 combinations that you are looking for.
Also this Map will have one entry per bucket, thus finding the value that you are interested in, will be really fast.
Computing the key that you need to get the value from is fairly trivial, you just need to set the bit (value that is not null).
String processTable(String cid, String postcode, String[] ratingvalue, String ratingType) {
if (cid != null) {
x = x | 1 << 3;
}
if (postCode != null) {
x = x | 1 << 2;
}
if (ratingValue != null) {
x = x | 1 << 1;
}
return map.get(x);
}
Do note that this code was take just as an exercise (well, we do have something close to this in real life, but there are compelling reasons for this - speed mainly).
Java 8 is not a magic wand you can simply wave at a piece of code to instantly improve it's readability.
Using null as a sentinel is bad practice and that's fundamentally why your code is hard to read. I suggest you rethink having nullable parameters.
Without fixing that, this is probably the best you can do:
if (ratingvalue == null)
{
if (cid != null && postcode != null) {
table = cassconf.getTable5();
}
else if (postcode != null) {
table = cassconf.getTable1();
}
else if (cid != null) {
table = cassconf.getTable3();
}
}
else
{
if (cid != null && postcode != null) {
table = cassconf.getTable6();
}
else if (postcode != null) {
table = cassconf.getTable2();
}
else if (cid != null) {
table = cassconf.getTable4();
}
}

Moq - Invocation count does not match

I appear to be having an issue when I go to verify the number of times a method has been called with certain arguments. I've pasted the relevant code and unit test below. My unit test is failing saying that the first Update of InventoryTransferItemTransaction is not being called at all; while the second is being called twice. I've tried simplifying out the arguments section to only look at the transferType; but still get the error. Debugging the code shows that the correct values are being passed through. It feels like I'm missing something, but can't quite put my finger on it.
Error messages:
Expected invocation on the mock once, but was 0 times: t => t.Update(It.Is<InventoryTransferItemTransaction>(args => (((((!(args.Created.IsNull()) && args.CreatedBy == User.GetNameFromContext()) && args.InventoryTransferItemId == 12) && !(args.Modified.IsNull())) && args.ModifiedBy == User.GetNameFromContext()) && args.Quantity == -10) && (Int32)args.TransactionType == 2))
Expected invocation on the mock once, but was 2 times: t => t.Update(It.Is<InventoryTransferItemTransaction>(args => (((((!(args.Created.IsNull()) && args.CreatedBy == User.GetNameFromContext()) && args.InventoryTransferItemId == 12) && !(args.Modified.IsNull())) && args.ModifiedBy == User.GetNameFromContext()) && args.Quantity == 0) && (Int32)args.TransactionType == 5))
Unit Test:
[Test]
public void CanRemoveTransferItem()
{
const int TRANSFER_ITEM_ID = 12;
const int QTY = 10;
var inventoryTransferFactoryMock = new Mock<IInventoryTransferFactory>();
inventoryTransferFactoryMock.Setup(t => t.GetTransferItem(TRANSFER_ITEM_ID))
.Returns(new InventoryTransferItem
{
InventoryTransferItemId = TRANSFER_ITEM_ID,
Quantity = QTY
});
inventoryTransferFactoryMock.Setup(t => t.CreateInventoryTransferItemTransaction())
.Returns(new InventoryTransferItemTransaction());
var inventoryTransferManager = new InventoryTransferManager(inventoryTransferFactoryMock.Object, null, null,
null, null);
inventoryTransferManager.RemoveTransferItem(TRANSFER_ITEM_ID);
inventoryTransferFactoryMock.Verify(t=>t.GetTransferItem(TRANSFER_ITEM_ID), Times.Once);
inventoryTransferFactoryMock.Verify(
t =>
t.Update(
It.Is<InventoryTransferItem>(
args =>
args.Quantity == 0 && args.ModifiedBy == User.GetNameFromContext() &&
!args.Modified.IsNull() && !args.ClosedDate.IsNull())), Times.Once);
inventoryTransferFactoryMock.Verify(t => t.CreateInventoryTransferItemTransaction(), Times.Exactly(2));
inventoryTransferFactoryMock.Verify(
t =>
t.Update(It.Is<InventoryTransferItemTransaction>(
args =>
!args.Created.IsNull() && args.CreatedBy == User.GetNameFromContext() &&
args.InventoryTransferItemId == TRANSFER_ITEM_ID &&
!args.Modified.IsNull() && args.ModifiedBy == User.GetNameFromContext() &&
args.Quantity == -QTY &&
args.TransactionType == InventoryTransferTransactionType.TransferAdjusted)), Times.Once);
inventoryTransferFactoryMock.Verify(
t =>
t.Update(It.Is<InventoryTransferItemTransaction>(
args =>
!args.Created.IsNull() && args.CreatedBy == User.GetNameFromContext() &&
args.InventoryTransferItemId == TRANSFER_ITEM_ID &&
!args.Modified.IsNull() && args.ModifiedBy == User.GetNameFromContext() &&
args.Quantity == 0 &&
args.TransactionType == InventoryTransferTransactionType.ClosedManually)), Times.Once);
}
RemoveTransferItem:
public void RemoveTransferItem(int inventoryTransferItemId)
{
using (var trx = new TransactionWrapper())
{
var transferItem = inventoryTransferFactory.GetTransferItem(inventoryTransferItemId);
transferItem.Modified = DateTime.Now;
transferItem.ModifiedBy = User.GetNameFromContext();
var originalQuantity = transferItem.Quantity;
transferItem.Quantity = 0;
transferItem.ClosedDate = DateTime.Now;
inventoryTransferFactory.Update(transferItem);
LogTransferItemTransaction(transferItem.InventoryTransferItemId, InventoryTransferTransactionType.TransferAdjusted, -originalQuantity);
LogTransferItemTransaction(transferItem.InventoryTransferItemId, InventoryTransferTransactionType.ClosedManually, 0);
trx.Complete();
}
}
LogTransferItemTransaction:
internal void LogTransferItemTransaction(int transferItemId, InventoryTransferTransactionType transferType, int quantity, int? employeeId = null)
{
var newTransaction = inventoryTransferFactory.CreateInventoryTransferItemTransaction();
newTransaction.Created = DateTime.Now;
newTransaction.CreatedBy = User.GetNameFromContext();
newTransaction.EmployeeId = employeeId;
newTransaction.InventoryTransferItemId = transferItemId;
newTransaction.Modified = DateTime.Now;
newTransaction.ModifiedBy = User.GetNameFromContext();
newTransaction.Quantity = quantity;
newTransaction.TransactionType = transferType;
inventoryTransferFactory.Update(newTransaction);
}
Figured it out. This line was the problem:
inventoryTransferFactoryMock.Setup(t => t.CreateInventoryTransferItemTransaction())
.Returns(new InventoryTransferItemTransaction());
It was returning the same object through the multiple iterations. Hence why the 2nd invocation appeared to be called twice while the first 0 times.
Changing it to this fixed the test:
inventoryTransferFactoryMock.Setup(t => t.CreateInventoryTransferItemTransaction())
.Returns(() => new InventoryTransferItemTransaction());