These days I had to work with OrmLite. I had to follow the convention of adding audit fields in all of the database tables. Instead of adding them manually, I wanted to populate them when using OrmLite SaveAsync() method. This is how to automatically insert and update audit fields with OrmLite.
1. Create a 1-to-1 mapping between two tables
Let’s store our favorite movies. Let’s create two classes, Movie and Director, to represent a one-to-one relationship between movies and their directors.
publicinterfaceIAudit{DateTimeCreatedDate{get;set;}DateTimeUpdatedDate{get;set;}}publicclassMovie:IAudit{[AutoIncrement]publicintId{get;set;}[StringLength(256)]publicstringName{get;set;}[Reference]// ^^^^^^^publicDirectorDirector{get;set;}[Required]publicDateTimeCreatedDate{get;set;}[Required]publicDateTimeUpdatedDate{get;set;}}publicclassDirector:IAudit{[AutoIncrement]publicintId{get;set;}[References(typeof(Movie))]// ^^^^^^publicintMovieId{get;set;}// ^^^^^// OrmLite expects a foreign key back to the Movie table[StringLength(256)]publicstringFullName{get;set;}[Required]publicDateTimeCreatedDate{get;set;}[Required]publicDateTimeUpdatedDate{get;set;}}
Notice we used OrmLite [Reference] to tie every director to his movie. With these two classes, OrmLite expects two tables and a foreign key from Director pointing back to Movie. Also, we used IAudit to add the CreatedDate and UpdateDate properties. We will use this interface in the next step.
2. Use OrmLite Insert and Update Filters
To automatically set CreatedDate and UpdatedDate when inserting and updating movies, let’s use OrmLite InsertFilter and UpdateFilter. With them, we can manipulate our records before putting them in the database.
Let’s create a unit test to show how to use those two filters,
usingServiceStack.DataAnnotations;usingServiceStack.OrmLite;namespaceOrmLiteAuditFields;[TestClass]publicclassPopulateAuditFieldsTest{[TestMethod]publicasyncTaskSaveAsync_InsertNewMovie_PopulatesAuditFields(){OrmLiteConfig.DialectProvider=SqlServerDialect.Provider;OrmLiteConfig.InsertFilter=(command,row)=>// ^^^^^{if(rowisIAuditauditRow){auditRow.CreatedDate=DateTime.UtcNow;// ^^^^^auditRow.UpdatedDate=DateTime.UtcNow;// ^^^^^}};OrmLiteConfig.UpdateFilter=(command,row)=>// ^^^^^{if(rowisIAuditauditRow){auditRow.UpdatedDate=DateTime.UtcNow;// ^^^^^}};varconnectionString="...Any SQL Server connection string here...";vardbFactory=newOrmLiteConnectionFactory(connectionString,SqlServerDialect.Provider);usingvardb=dbFactory.Open();varmovieToInsert=newMovie{Name="Titanic",// We're not setting CreatedDate and UpdatedDate here...Director=newDirector{FullName="James Cameron"// We're not setting CreatedDate and UpdatedDate here, either...}};awaitdb.SaveAsync(movieToInsert,references:true);// ^^^^^// We insert "Titanic" for the first time// With "references: true", we also insert the directorvarinsertedMovie=awaitdb.SingleByIdAsync<Movie>(movie.Id);Assert.IsNotNull(insertedMovie);Assert.AreNotEqual(default,insertedMovie.CreatedDate);Assert.AreNotEqual(default,insertedMovie.UpdatedDate);}}
Notice we defined the InsertFilter and UpdateFilter and inside them, we checked if the row to be inserted or updated implemented the IAudit interface, to then set the audit fields with the current timestamp.
To insert a movie and its director, we used SaveAsync() with the optional parameter references set to true. We didn’t explicitly set the CreatedDate and UpdatedDate properties before inserting a movie.
Internally, OrmLite SaveAsync() either inserts or updates an object if it exists in the database. It uses the property annotated as the primary key to find if the object already exists in the database.
Instead of using filters, we can use [Default(OrmLiteVariables.SystemUtc)] to annotate our audit fields. With this attribute, OrmLite will create a default constraint. But, this will work only for the first insertion. Not for future updates on the same record.
3. Add [IgnoreOnUpdate] for future updates
To support future updates using the OrmLite SaveAsync(), we need to annotate the CreatedDate property with the attribute [IgnoreOnUpdate] in the Movie and Director classes. Like this,
Internally, when generating the SQL query for an UPDATE statement, OrmLite doesn’t include properties annotated with [IgnoreOnUpdate]. Source Also, OrmLite has similar attributes for insertions and queries: [IgnoreOnInsertAttribute] and [IgnoreOnSelectAttribute]
Let’s modify our previous unit test to insert and update a movie,
usingServiceStack.DataAnnotations;usingServiceStack.OrmLite;namespaceOrmLiteAuditFields;[TestClass]publicclassPopulateAuditFieldsTest{[TestMethod]publicasyncTaskSaveAsync_InsertNewMovie_PopulatesAuditFields(){// Same OrmLiteConfig as before...varconnectionString="...Any SQL Server connection string here...";vardbFactory=newOrmLiteConnectionFactory(connectionString,SqlServerDialect.Provider);usingvardb=dbFactory.Open();varmovieToInsert=newMovie{Name="Titanic",// We're not setting CreatedDate and UpdatedDate here...Director=newDirector{FullName="James Cameron"// We're not setting CreatedDate and UpdatedDate here, either...}};awaitdb.SaveAsync(movieToInsert,references:true);// ^^^^^// 1.// We insert "Titanic" for the first time// With "references: true", we also insert the directorawaitTask.Delay(1_000);// Let's give it some time...varmovieToUpdate=newMovie{Id=movie.Id,// ^^^^^Name="The Titanic",// We're not setting CreatedDate and UpdatedDate here...Director=newDirector{Id=movie.Director.Id,// ^^^^^FullName="J. Cameron"// We're not setting CreatedDate and UpdatedDate here, either...}};awaitdb.SaveAsync(movieToUpdate,references:true);// ^^^^^// 2.// To emulate a repository method, for example,// We're creating a new Movie object updating// movie and director names using the same Ids}}
Often, when we work with repositories to abstract our data access layer, we update objects using the identifier of an already-inserted object and another object with the properties to update. Something like, UpdateAsync(movieId, aMovieWithSomePropertiesChanged).
Notice this time, after inserting a movie for the first time, we created a separate Movie instance (movieToUpdate) keeping the same ids and updating the other properties. We used the same SaveAsync() as before.
At this point, if we don’t annotate the CreatedDate properties with [IgnoreOnUpdate], we get the exception: “System.Data.SqlTypes.SqlTypeException: SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.”
We don’t want to change the CreatedDate on updates. That’s why in the UpdateFilter we only change UpdatedDate. Since we’re using a different Movie instance in the second SaveAsync() call, CreatedDate stays uninitialized when OrmLite runs the UPDATE statement in the database. That’s why we got that exception.
Voilà! That’s how to automate audit fields with OrmLite. After reading the OrmLite source code, I found out about these filters and attributes. I learned the lesson of reading our source code dependencies from a past Monday Links episode.
These days I needed to rename all occurrences of one keyword with another in source files and file names. In one of my client’s projects, I had to query one microservice to list a type of account to store it in an intermediate database. After a change in requirements, I had to query for another type of account and rename every place where I used the old one. This is what I learned.
1. Find and Replace inside Visual Studio
My original solution was to use Visual Studio to replace “OldAccount” with “NewAccount” in all .cs files in my solution. I used the “Replace in Files” menu by pressing: Ctrl + Shift + h,
Visual Studio 'Replace in Files' menu
After this step, I replaced all occurrences inside source files. For example, it renamed class names from IOldAccountService to INewAccountService. To rename variables, I repeated the same replace operation but using lowercase patterns.
With the “Replace in Files” menu, I covered file content. But I still had to change the filenames. For example, I needed to rename IOldAccountService.cs to INewAccountService.cs. I did it by hand. Luckily, I didn’t have to replace many of them. There must be a better way!- I thought.
2. Find and Replace with Bash
After renaming my files by hand, I thought I could have used the command line to replace both the content and file names. I use Git Bash anyways. Therefore I have access to most of Unix commands.
Replace ‘old’ with ‘new’ inside all .cs files
This is how to replace “Old” with “New” in all .cs files, Source
With the grep command, we look for all .cs files (--include \*.cs) containing the “Old” keyword, no matter the case (-i flag), inside all child folders (-r), showing only the file path (-l flag).
We could use the first command, before the pipe, to only list the .cs files containing a keyword.
Then, with the sed command, we replace the file content in place (-i flag), changing all occurrences of “Old” with “New” (s/Old/New/g). Notice the g option in the replacement pattern. To avoid messing with line endings, we use the -b flag. Source
If we use spaces in filenames, that’s weird in source files but just in case, we need to tell grep and sed to use a different separator,
This time, we’re using the find command to “find” all files (-type f), with “Old” anywhere in their names (-name "*Old*"), inside the current folder (.), excluding the TestCoverageReport folder (-path ./TestCoverageReport -prune).
Optionally, we can exclude multiple files by wrapping them inside parenthesis, like, Source
find .\(-path ./FolderToExclude -o-path ./AnotherFolderToExclude \)\-prune-type f -o-name"*Old*"
Then, we feed the sed command to generate new names replacing “Old” with “New.” This time, we’re using the p option to print the “before” pattern. Up to this point, our command returns something like this,
With the last part, we split the sed output by the newline character and passed groups of two filenames to the mv command to finally rename the files.
Another alternative to sed followed by mv would be to use the rename command, like this,
find .-path ./TestCoverageReport -prune-type f -o-name"*Old*"\
| xargs rename 's/Old/New/g'
Voilà! That’s how to replace a keyword in the content and name of files. It took me some time to figure this out. But, we can rename files with two one-liners. It will save us time in the future. Kudos to StackOverflow.
These days I had to rename all the projects inside a Visual Studio solution and the folders containing them. From SomeThing.Core to Something.Core. That wasn’t the exact typo. But that’s the idea. Here it’s what I learned. It wasn’t as easy as only renaming the projects in Visual Studio.
1. Rename folders and projects manually
After digging into it, I found this StackOverflow answer to rename folders and projects inside a solution. It layouts a checklist of what to do. Here I’m bringing it with some extra steps:
First, close Visual Studio.
Then, create a backup of the .sln file. Just in case.
Next, use git mv to rename the folder from SomeThing to Something. This preserves the history of the file. Also, rename the csproj files to match the new name.
Use the next bash script to rename the folders and csproj files inside the src and tests folders.
# Replace these two values, pleaseold=SomeThing
new=Something
# Put here all others folders...for folder in'Core''Infrastructure'do
git mv src/$old.$folder src/$old.$folder.Temp
git mv src/$old.$folder.Temp src/$new.$folder
git mv src/$new.$folder/$old.$folder.csproj src/$new.$folder/$new.$folder.csproj
git mv tests/$old.$folder.Tests tests/$old.$folder.Tests.Temp
git mv tests/$old.$folder.Tests.Temp tests/$new.$folder.Tests
git mv src/$new.$folder/$old.$folder.csproj src/$new.$folder/$new.$folder.csproj
done
Notice that it uses a temp folder to rename the containing SomeThing folder. I found that workaround in this StackOverflow answer. Git doesn’t go along with different casings in file names.
Next, in the .sln file, edit all instances of SomeThing to be Something, using a text editor like NotePad++.
Next, restart Visual Studio, and everything will work as before, but with the project in a different directory.
Next, right-click on the solution name and run “Sync Namespaces.”
Fix all namespaces with 'Sync Namespaces' option
Next, use “Replace in Files” to clean leftovers. For example, launchSettings.json and appSettings.json files.
Fix all other leftovers with 'Replace In Files' option
Everything should compile. Horraaay!
That was a tedious process. Especially if, like me, you have to rename all source and tests projects in a big solution.
Given the amount of votes of the StackOverflow answer I followed, more than 400, I bet somebody else thought about automating this process.
Indeed.
2. Rename folders and projects with ProjectRenamer
ProjectRenamer is a dotnet tool that does all the heavy and repetitive work for us. It renames the folders, csproj files, and project references. Also, it could create a Git commit and build the solution.
ProjectRenamer expects the Git working directory to be clean. Stash or commit all other changes before using it.
Using a Powershell prompt, from the folder containing the .sln file, run:
With the --no-commit flag, ProjectRenamer will only stage the files. And, the --no-review skips the user confirmation. It’s like the -f flag of some Unix commands.
Last time, in the Unit Testing 101 series, we refactored a unit test for a method that fed a report of transactions in a payment system. This time, let’s refactor another test. This test is based on a real test I had to refactor in one of my client’s projects.
Before looking at our test, a bit of background. This test belongs to a two-way integration between a Property Management System and a third-party service. Let’s call it: Acme Corporation. To connect one of our properties to Acme, we go throught an OAuth flow.
A bit of background on OAuth flows
To start the OAuth flow, we call an Authorize endpoint in a web browser. Acme prompts us to enter a user and password. Then, they return a verification code. With it, we call a Token endpoint to grab the authentication and refresh tokens. We use the authentication token in a header in future requests.
Apart from the authentication and refresh codes, to make this integration work in both ways, we create some random credentials and send them to Acme. With these credentials, Acme calls some public endpoints on our side.
Here’s the test to refactor
With this background, let’s look at the test we’re going to refactor. This is an integration test that checks that we can create, update and retrieve Acme “connections” in our database.
Yes, that’s the real test. “Some names have been changed to protect the innocent.” Can you take a look and identify what our test does?
To be fair, here’s the AcmeConnection class with the signature of Load() and other methods,
publicrecordLightspeedConnection(PmsPropertyIdPmsPropertyId){publicstaticAcmeConnectionLoad(AcmeConnectionIddbId,ClientIdclientId,AcmeCompany?acmeCompany=null,Pkce?pkce=null,AcmeCredentials?acmeCredentials=null,OurCredentials?ourCredentials=null){// Create a new AcmeConnection from all the parameters// Beep, beep, boop...}// A bunch of methods to update the AcmeConnection statepublicvoidGeneratePkce(){/* ... */}publicvoidUpdateAcmeCompany(AcmeCompanycompany){/* ... */}publicvoidUpdateAcmeCredentials(AcmeCredentialscredentials){/* ... */}publicvoidSetOurCredentialsAsync(IAcmeServiceservice){/* ... */}}
The Pkce object corresponds to two security codes we exchange in the OAuth flow. For more details, see Dropbox guide on PKCE.
Did you spot what our test does? Don’t worry. It took me some time to get what this test does, even though I was familiar with that codebase.
That test is full of noise and hard to follow. It abuses the acmeConnection variable. It keeps reading and assigning connections to it.
Behind all that noise, our test creates a new connection and stores it. Then, it retrieves, mutates, and updates the same connection. And in the last step, it recreates another one from all the input values to use it in the Assert part.
Let’s see the test again, annotated this time,
[Fact]publicasyncTaskGetConnectionAsync_ConnectionUpdated_ReturnsUpdatedConnection(){varrepository=newAcmeConnectionRepository(AnySqlConnection);varacmeConnection=newAcmeConnection(ClientId);varacmeConnectionId=awaitrepository.CreateAcmeConnectionAsync(acmeConnection);// 1. Create connection ^^^^^acmeConnection.GeneratePkce();acmeConnection=AcmeConnection.Load(acmeConnectionId,ClientId,pkce:acmeConnection.Pkce,acmeCredentials:AcmeCredentials,ourCredentials:OurCredentials.GenerateCredentials(ClientId));// ^^^^^// 2. Change both credentialsawaitrepository.UpdateAcmeConnectionAsync(acmeConnection);varconnectionFromDb=awaitrepository.GetAcmeConnectionAsync(ClientId);// ^^^^^// 3. Retrieve the newly created connectionacmeConnection=AcmeConnection.Load(acmeConnectionId,ClientId,AcmeCompany,connectionFromDb!.Pkce);// ^^^^^acmeConnection.GeneratePkce();// ^^^^acmeConnection=AcmeConnection.Load(acmeConnectionId,ClientId,AcmeCompany,acmeConnection.Pkce,connectionFromDb.AcmeCredentials,connectionFromDb.OurCredentials);acmeConnection.UpdateAcmeCredentials(OtherAcmeCredentials);// ^^^^^awaitacmeConnection.SetOurCredentialsAsync(_acmeConnectionServiceMock.Object);// ^^^^^// 4. Change Acme company and both credentials againawaitrepository.UpdateAcmeConnectionAsync(acmeConnection);// ^^^^^// 5. UpdatevarupdatedConnectionFromDb=awaitrepository.GetAcmeConnectionAsync(newClientId(ClientId));acmeConnection=AcmeConnection.Load(// ^^^^^acmeConnectionId,ClientId,AcmeCompany,Pkce.Load(acmeConnection.Pkce!.Id!,acmeConnection.Pkce.CodeVerifier,updatedConnectionFromDb.Pkce!.CreatedDate,updatedConnectionFromDb.Pkce.UpdatedDate),AcmeCredentials.Load(acmeConnection.AcmeCredentials!.Id!,acmeConnection.AcmeCredentials.RefreshToken,acmeConnection.AcmeCredentials.AccessToken,acmeConnection.AcmeCredentials.AccessTokenExpiration,updatedConnectionFromDb.AcmeCredentials!.CreatedDate,updatedConnectionFromDb.AcmeCredentials.UpdatedDate),OurCredentials.Load(acmeConnection.OurCredentials!.Id!,acmeConnection.OurCredentials.Username,acmeConnection.OurCredentials.Password,updatedConnectionFromDb.OurCredentials!.CreatedDate,updatedConnectionFromDb.OurCredentials.UpdatedDate));Assert.NotNull(connectionFromDb);Assert.NotNull(updatedConnectionFromDb);Assert.Equal(acmeConnectionId,connectionFromDb!.Id);Assert.Equal(acmeConnectionId,updatedConnectionFromDb!.Id);Assert.Equal(acmeConnection,updatedConnectionFromDb);Assert.NotEqual(acmeConnection,connectionFromDb);}
Also, this test keeps using the Load() method, even though the AcmeConnection class has some methods to update its own state.
Step 1. Use the same code as the Production code
Write integration tests using the same code as the production code.
Let’s write our test in terms of our business methods instead of using the Load() everywhere.
[Fact]publicasyncTaskGetConnectionAsync_ConnectionUpdated_ReturnsUpdatedConnection(){varrepository=newAcmeConnectionRepository(AnySqlConnection);varacmeConnection=newAcmeConnection(ClientId);varacmeConnectionId=awaitrepository.CreateAcmeConnectionAsync(acmeConnection);// 1. Create connection ^^^^^acmeConnection=awaitrepository.GetAcmeConnectionAsync(ClientId);acmeConnection.GeneratePkce();// ^^^^^awaitrepository.UpdateAcmeConnectionAsync(acmeConnection);// ^^^^^// 2. Update pkceacmeConnection=awaitrepository.GetAcmeConnectionAsync(ClientId);acmeConnection.UpdateAcmeCompany(AcmeCompany);// ^^^^^acmeConnection.UpdateAcmeCredentials(OtherAcmeCredentials);// ^^^^^awaitacmeConnection.SetOurCredentialsAsync(_acmeConnectionServiceMock.Object);// ^^^^^awaitrepository.UpdateAcmeConnectionAsync(acmeConnection);// ^^^^^// 3. Update company and credentialsvarupdatedConnectionFromDb=awaitrepository.GetAcmeConnectionAsync(ClientId);Assert.NotNull(updatedConnectionFromDb);Assert.Equal(acmeConnectionId,updatedConnectionFromDb!.Id);Assert.Equal(acmeConnection.Pkce,updatedConnectionFromDb.Pkce);Assert.Equal(acmeConnection.AcmeCompany,updatedConnectionFromDb.AcmeCompany);Assert.NotNull(updatedConnectionFromDb.AcmeCredentials);Assert.NotNull(updatedConnectionFromDb.OurCredentials);}
Notice, we stopped using the Load() method. We rewrote the test using the methods from the AcmeConnection class like UpdateAcmeCredentials, SetOurCredentialsAsync, and others.
Also, we separated the test into blocks. In each block, we retrieved the acmeConnection, mutated it with its own methods, and called UpdateAcmeConnectionAsync(). Cleaner!- I’d say.
We removed the last Load() call. We didn’t need to assert if the last retrieved object was exactly the same as the recreated version. Instead, we checked that the updated connection had the same value objects.
Step 2. Use descriptive variables
For the next step, let’s stop abusing the same acmeConnection variable and create more descriptive variables for every step.
With these variables names is easier to follow what our test does.
An alternative solution with Factory methods
We were lucky there were a lot of methods on the AcmeConnection class to mutate and update it in the tests. If we didn’t have those methods, we could create one “clone” method for every property we needed to mutate.
For example,
publicstaticclassAcmeConnectionExtensions{publicstaticAcmeConnectionCredentialsFrom(thisLightspeedConnectionself,AcmeCredentialsacmeCredentials,OurCredentialsourCredentials){// Copy self and change AcmeCredentials and OurCredentials}publicstaticAcmeConnectionAcmeCompanyFrom(thisLightspeedConnectionself,AcmeCompanyacmeCompany){// Copy self and change the AcmeCompany}}
We can create an initial AcmeConnection and clone it with our helper methods to reduce all boilerplate in our original test.
Voilà! That was a long refactoring session. There are two things we can take away from this refactoring. First, we should strive for readability in our tests. We should make our test even more readable than our production code. Can anyone spot what one of our tests does in 30 seconds? That’s a readable test. Second, we should always write our tests using the same code as our production code. We shouldn’t write production code to only use it inside our unit tests. That Load() method was a backdoor to build objects when we should have used class constructors and methods to mutate its state.
Names are important in programming. Good names could be the difference between a developer nodding his head in agreement or making funny faces in a “Wait, whaaaat?” moment. Names are so important that the Clean Code and The Art of Readable Code devote entire chapters to the subject. These are some words I’m banning from my method and class names.
1. Get and Set in method names
I wish I could remember what Kevlin Henney’s presentation has this idea. He argues that “Get” and “Set” are some words with more meanings in an English dictionary. Then why do we use them in our code when our names should be the least ambiguous as possible? He has a point!
These days I reviewed a pull request that had a code block that reminded me about this point. It looked like this,
Maybe WithReservationId() or simply ReservationId() would be better alternatives. Even an old auto-implemented property would get our backs covered here.
The next names I’m banning are the “Utility” and “Helper” suffixes in class names. Every time I see them, I wonder if the author (and I) missed an opportunity to create domain entities or better named classes.
In one of my client’s projects, I had to work with a class that looked like this,
publicstaticclassMetadataHelper{publicstaticvoidAddFeeRates(Feefee,PaymentRequestrequest,IDictionary<string,string>metadata){// Doing something with 'fee' and 'request' to populate 'metadata'...}publicstaticvoidAddFeeRates(Feefee,StripePaymentIntentpaymentIntent,IDictionary<string,string>metadata){// Doing something with 'fee' and 'paymentIntent' to populate 'metadata'...}}
It was a class that generated some payment metadata based on payment fees and requests. Somebody took the easy route and dumped everything in a static MetadataHelper class.
Instead, we could write a non-static PaymentMetadata class to wrap the metadata dictionary. Like this,
publicclassPaymentMetadata{privatereadonlyIDictionary<string,string>_metadata;publicPaymentMetadata(IDictionary<string,string>baseMetadata){_metadata=baseMetadata;}publicvoidAddFeeRates(Feefee,PaymentRequestrequest){// Doing something with 'fee' and 'request' to expand 'metadata'...}publicvoidAddFeeRates(Feefee,StripePaymentIntentpaymentIntent){// Doing something with 'fee' and 'paymentIntent' to expand 'metadata'...}publicIDictionary<string,string>ToDictionary()=>_metadata;}
If a concept is important inside the business domain, we should promote it out of helper classes.
Often, we use Utility and Helper classes to dump all kinds of methods we couldn’t find a good place for.
3. Constants classes
This isn’t exactly a name. But the last thing I’m banning is Constant classes. I learned this lesson after reading Domain Modeling Made Functional.
Recently, I found some code that looked like this,
It was a class full of unrelated constants. Here, I only showed five of them. Among those, I found the types of transactions in a reservation management system.
On the caller side, a method that expects any of the TransactionTypeId uses an int parameter. For example,
But, any int won’t work. Only those inside the Constants class are the valid ones.
This gets worse when Constant classes start to proliferate, and every project of a solution has its own Constants class. Arggggg!
Instead of Constants classes, let’s use enums to restrict the values we can pass to methods. Or, at least, let’s move the constants closer to where they’re expected, not in a catch-all class. With an enum, the compiler helps us to check if we are passing a “good” value.
Using an enum, our previous example looks like this,
Voilà! These are the names I’m banning in my own code. And I wish I could ban them in code reviews too. Are you also guilty of any of the three? I’ve been there and done that.