On ACF2023, we're regularly seeing the dreaded 'Br...
# adobe
d
On ACF2023, we're regularly seeing the dreaded 'Branch target offset too large for short' error. My understanding is that there's too many conditionals in the cffunction for the JVM to handle. Our function is large, but isn't 'crazy' large, so this seems like a bit of a cop out. Is there something that can be done to stop this from happening (a magic setting somewhere?). We can't trim down the conditionals any more than we have, and cfincluding in a cfc feels like a ugly hack, but seems like our only option. Any suggestions? TIA
s
That's an underlying error from the JVM and you should never run into that with well-structured code, IMO. If your function is getting that error it is too large and needs to be refactored. There's no workaround: fix your code.
2
And, yes, it's possible that ACF2023 is generating more code than earlier versions but the bottom line is still: your code is terrible and needs to be refactored, sorry.
👆 1
d
It's actually not bad code, subjective I guess. It's a big SQL statement in the datalayer that has many conditionals. Breaking it into multiple functions would be a nightmare to maintain / understand. Having it all there makes the most sense. Anyway, thanks for confirming there's no other way.
a
I've hit this before working with legacy code which people have just added to over the years and you just have to tackle it. You say We can't trim down the conditionals any more than we have why can't you move conditionals into functions? Can you give a example or code you can't refactor?
Ah - posted at the same time - so it's a big ol' SQL statement?
d
Yep
You really want to see it all in one place. Splitting it up would be terrible to understand what's going on.
It's not even that big in my opinion. Seems like CF / JVM should give me a little more room!
a
Sounds like using an SQL builder would be the solution - do you have tests?
d
Yeah nah, been down that road before. So if I simply CFINCLUDE the entire thing in a CFC function my problems disappear? Seems nuts if that's the solution others are suggesting on stackoverflow.
a
I've never tried using cfinclude to solve it - I've always refactored so don't know if that solves it.
👍 1
I think it must be the conditionals that cause it though. So do you have lots of
if (x == 1 && y > 2 || a > 3 ) {..SQL WHERE...}
type stuff?
d
Yes lots of ifs. Mainly to reduce the amount of data the query returns in different conditions. Improves performance. Think a getProducts() on a big ecom store. Lots and lots of different scenarios.
Ok back to the drawing board.
a
That conditional can be pulled out into it's own function though - and by putting into a function you can describe what it is doing.
That's how I'd start tackling it - conditionals as functions, particularly the ones that have more than one thing in them
d
Like #includePrices()# Where includePrices() returns a SQL statement text to get the prices?
a
No
<cfif includePrices(x,y)>pricescolumn,</cfif>
Conditionals are boolean
That way you move the logic out of the SQL construction, but the SQL is still completely readable
d
Mmm most of our conditionals are boolean already, so re-writing as a function probably won't win us much I'm guessing. Very little or few are multiple statements
a
You can even go further and have
includePrices
in it's own CFC so that the logic is separate (and therefore testable and reusable)
d
Yep I hear you on that. But does start to break down the readability. In my opinion. You now have to work your way through a load of functions to edit a SQL statement.
a
It's a worthwhile refactor anyway to move the logic and will help make the code readable, testable and will also reduce it - may not get you all the way though I appreciate that. An SQL builder is really the right solution but scary to refactor that with zero tests
1
d
There's no 'reuse' of these functions. The only reason they are there is to deal with this limitation! If the statements were used in multiple queries, sure, there's value.
SQL builder, sure, but yes. a significant piece of refactoring.
a
The only reason they are there is to deal with this limitation!
Disagree - it makes you code testable - and as professionals we should all be writing tests. Ask the crowdstrike people about testing :D
a
Just to pitch in: The function is doing too much as you describe it. It should either build the SQL statement or run the SQL statement, not both. And the building-the-SQL part could perhaps use a Template Method approach where one has something like
Copy code
buildTheStateemnt() {
    buildTheSelect()
    buildTheFrom()
    // etc
}
Refactoring (and keeping one's methods small and doing one thing) is not about reusability btw. it's clarity and testability.
👆🏻 1
You now have to work your way through a load of functions to edit a SQL statement.
Nope. Now you just have to look at the part of the SQL statement you currently need to focus on, and don't have to worry about the rest of it.
d
I disagree. Feels super ugly that way. I don't think anyone would start out that way. SELECT id, name FROM sometable WHERE something = 1 Would never be written buildTheStateemnt() { buildTheSelect() buildTheFrom() buildTheWhere() } If you do that, man, you're nuts and good luck to you!
I know you're nuts by the way Adam.
😂 1
a
Copy code
SELECT id, name
FROM sometable
WHERE something = 1

Would never be written

buildTheStateemnt() {
    buildTheSelect()
    buildTheFrom()
    buildTheWhere()
}
False equivalence. No-one's talking about refactoring a 50-byte SQL statement to use the template pattern. It's cos you've let it get away from you AND THE CODE NOW DOESN'T EVEN COMPILE (FFS!!!) that you are needing to ask how to mitigate it.
Don't ask the question if you don't want the advice.
2
There's an entire dev industry that has lived through this already and come up with these design patterns to mitigate these things. And they're pretty much universally accepted as a reasonable approach.
d
In the past at least, you couldn't use cfqueryparam anywhere except between cfquery tags. Not even in an included or cfmodule'd file that was only used between cfquery tags. Has that constraint been removed? It always annoyed me greatly, because it forced you into giant monolithic code.
a
Interesting. CF used to allow this:
Copy code
<cfquery>
    <cfinclude><!--- params in here --->
</cfquery>
We had a lot of that back when I used to write code like Dave's (at the same company as Dave, even... hence him knowing I'm, nuts). Haven't tried anything like that for 15yrs or so though. But one only needs
<cfqueryparam>
if one still uses
<cfquery>
. Not needed with
queryExecute
(or even with the Query.cfc thing, from memory). It's a bit of an old-school approach to things.
Still works fine in CF:
Copy code
<!--- test.cfm --->
<cfquery name="numbers">
	SELECT *
	FROM numbers
	<cfinclude template="./filters.cfm">
	ORDER BY id
</cfquery>
<cfdump var="#numbers#">

<!--- filters.cfm --->
WHERE id > <cfqueryparam value="5" cfsqltype="CF_SQL_INTEGER">
(that's CF2021)
(which also shows I have an error in my test data... the
en
value should be
six
. But you get the idea)
d
Interesting, noted, works in CF2021 too. And yes old school.
m
I don't have anything else to add that hasn't been succinctly and correctly stated by Adam, Sean, et al. I will say I enjoy these kinds of architectural discussions. I may have to unleash my buddy in here soon. He's a principal architect level C# dev who I convinced to learn CF. In 6 days he's writing absolutely incredible code in CF, just beautiful stuff. He would eat this discussion up. 😄
a
changes name to 'et al'
❤️ 2
m
You'd be SO famous. 😄
😄 1
a
me and my buddy weird al
m
Yes, everyone is talking about Al. He's having a revival!
🙃 1
g
I write a lot of SQL logic with optional CTEs, optional inner/outer joins & subqueries using cfscript. Normally this would be a nightmare to write natively, but I used Ortus QB with my ACF app (without CommandBox, Forgebox or ColdBox) and, after working with it for a bit, found it to be easier to troubleshoot & unit test. QB (Query Builder) enables you to build a simple or complex SQL queries and then allow you to output or execute it. https://github.com/coldbox-modules/qb NOTE: If you want to use QB with any deprecated versions of ACF (ie, 2016 or 11), you'll need to download the v8.10.0 version.
s
Yup, query builders are a great way to go for anything involving conditionals. I work in Clojure these days and there's a library called HoneySQL that we use heavily at work for this (and I've ended up as the maintainer of it, since we rely on it so much).
d
Thanks @gamesover, very helpful.